What It Is
The addOrCondition method creates OR relationships between query conditions in GlideRecord operations, solving the fundamental problem of building complex queries that require "this OR that" logic rather than the default "this AND that" behavior. Unlike addQuery which always creates AND conditions, addOrCondition must be chained to the query condition object returned by the preceding addQuery call. This architectural constraint exists because ServiceNow needs to maintain the proper grouping of OR conditions within the broader query structure. The method operates at the database query layer, translating your chained conditions into proper SQL WHERE clauses with parenthetical grouping.
Architecturally, addOrCondition executes exclusively on the server side—there's no client-side equivalent because client scripts shouldn't be performing direct database queries. You'll use this in Business Rules, Script Includes, Scheduled Scripts, and any server-side scripting context where you need to query records with complex conditional logic. The method works by modifying the GlideRecord's internal query object before the database call is made, which means all your OR conditions must be defined before calling query() or any method that triggers query execution like hasNext() or getRowCount().
Under the hood, ServiceNow's query engine processes your chained OR conditions by building a proper SQL query with parenthetical grouping. When you chain multiple addOrCondition calls to the same query object, ServiceNow groups them into a single OR clause, then ANDs that entire group with any other query conditions on the GlideRecord. This creates queries like "WHERE (condition1 OR condition2 OR condition3) AND other_condition". The database layer handles the actual execution, but ServiceNow's ORM translates your method calls into the appropriate SQL syntax while respecting ACLs, business rules, and other platform constraints that raw SQL wouldn't honor.
Without addOrCondition, you're forced into writing encoded query strings by hand or making multiple separate database calls and merging results in memory—both approaches that are error-prone and perform poorly. Complex queries involving "find all incidents that are either high priority OR assigned to the security team OR have been open longer than 30 days" become unwieldy without proper OR logic. You could technically achieve the same result using addEncodedQuery with manually constructed query strings, but that approach is brittle, harder to maintain, and loses the type safety and readability of method chaining. The alternative of running multiple queries and combining results client-side is both slower and more complex to implement correctly.
Developers and architects use addOrCondition primarily in Business Rules for record filtering, Script Includes for utility functions, and Scheduled Scripts for maintenance operations. Admins typically don't interact with this method directly, though they might encounter it in custom scripts they're maintaining or troubleshooting. The method is essential in enterprise implementations where business logic requires complex conditional queries—think assignment rules that check multiple criteria, reporting functions that aggregate across different record states, or cleanup jobs that target records matching various patterns. You'll also see it heavily used in integration scenarios where external systems send complex filter requirements that need translation into ServiceNow queries.
The method relates closely to addQuery as its prerequisite—you can't use OR conditions without a base query to attach them to. It also pairs with addNullQuery and addNotNullQuery since these methods also return query objects that support OR chaining. Understanding addOrCondition is prerequisite knowledge for working with GlideAggregate's similar OR functionality and for understanding how ServiceNow's query engine processes complex conditional logic across the platform.
How It Works Under the Hood
When you call addQuery on a GlideRecord, ServiceNow doesn't immediately execute a database query. Instead, it returns a QueryCondition object that represents that specific condition and stores it in the GlideRecord's internal query builder. This QueryCondition object has its own methods, including addOrCondition, which allows you to chain additional conditions that will be ORed with the original condition. The query builder maintains a tree structure of these conditions, grouping OR conditions together and maintaining AND relationships between different condition groups.
The actual database query construction happens when you call a method that triggers query execution—query(), hasNext(), or get(). At this point, ServiceNow's ORM layer traverses the condition tree, converts each condition into appropriate SQL syntax, and applies proper parenthetical grouping. The platform also injects any applicable ACL conditions, domain restrictions, and other security constraints that your script context requires. This deferred execution model allows you to build complex queries programmatically without worrying about the underlying SQL generation, while ensuring that platform security and business rules are always enforced.
The Query Building Lifecycle
- Call
addQuery()on the GlideRecord, which creates a QueryCondition object and stores it in the GlideRecord's condition tree - Chain
addOrCondition()calls to the QueryCondition object, which groups these conditions as siblings in the OR group - Additional
addQuery()calls on the GlideRecord create separate condition groups that will be ANDed with the OR group - Query execution triggers the ORM to traverse the condition tree and generate SQL with proper parenthetical grouping
- Platform security layers inject ACL conditions, domain restrictions, and other constraints into the final query
- Database executes the final SQL query and returns results to the GlideRecord for iteration
Enjoying this? Get one deep-dive per week.
Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.
The Core Pattern
// Business Rule: Before Insert/Update on incident table
(function executeRule(current, previous) {
// Query for available assignees using OR conditions
var gr = new GlideRecord('sys_user');
// Start with the base condition and store the query object
var priorityQuery = gr.addQuery('department', 'IT Support');
// Chain OR conditions to the same query object
priorityQuery.addOrCondition('department', 'Security');
priorityQuery.addOrCondition('roles', 'CONTAINS', 'incident_manager');
// Additional AND condition on the GlideRecord itself
gr.addQuery('active', true);
gr.addQuery('availability', '!=', 'busy');
gr.query();
// Process results for assignment logic
while (gr.next()) {
// Assignment logic here
}
})(current, previous);var UserQueryUtils = Class.create();
UserQueryUtils.prototype = {
// Utility function to find users matching multiple role criteria
findUsersByRoleOptions: function(roleList, includeInactive) {
var gr = new GlideRecord('sys_user');
// Build OR conditions for multiple roles
if (roleList && roleList.length > 0) {
var roleQuery = gr.addQuery('roles', 'CONTAINS', roleList[0]);
// Add each additional role as an OR condition
for (var i = 1; i < roleList.length; i++) {
roleQuery.addOrCondition('roles', 'CONTAINS', roleList[i]);
}
}
// AND condition for active status unless overridden
if (!includeInactive) {
gr.addQuery('active', true);
}
return gr; // Return unexecuted GlideRecord for further chaining
},
type: 'UserQueryUtils'
};Real-World Scenarios
Priority-Based Incident Escalation
Your organization needs to automatically escalate incidents that meet any of several urgent criteria: high/critical priority, security-related category, or affecting VIP users. A scheduled job runs every 15 minutes to identify these incidents for immediate attention.
// Find incidents requiring immediate escalation
var gr = new GlideRecord('incident');
// Base condition: incidents open longer than 2 hours
var escalationQuery = gr.addQuery('opened_at', '<', gs.hoursAgoStart(2));
// OR conditions for priority-based escalation
escalationQuery.addOrCondition('priority', 'IN', '1,2'); // High/Critical
escalationQuery.addOrCondition('category', 'security');
escalationQuery.addOrCondition('caller_id.vip', true);
// AND conditions that always apply
gr.addQuery('state', 'IN', '1,2,3'); // New, In Progress, On Hold
gr.addQuery('escalation', 0); // Not already escalated
gr.addQuery('assigned_to', '!=', '');
gr.query();
while (gr.next()) {
// Trigger escalation workflow
gr.escalation = 1;
gr.update();
}Watch for the VIP user condition—caller_id.vip uses dot-notation which can be tricky with OR conditions if the reference field is null. Consider adding a null check or using addNullQuery as an additional OR condition if your data quality isn't perfect. The escalation field check prevents double-processing, but remember this pattern only works if your escalation workflow consistently sets that field.
Multi-Department Approval Routing
Service catalog requests need dynamic approval routing based on requested items, cost thresholds, or requester department. The approval workflow needs to find all possible approvers who can handle the specific combination of criteria.
// Find eligible approvers for this request
var gr = new GlideRecord('sys_user');
// Department-based approval authority
var approverQuery = gr.addQuery('department', current.requested_for.department);
// OR: Users with specific approval roles
approverQuery.addOrCondition('roles', 'CONTAINS', 'catalog_approver');
approverQuery.addOrCondition('roles', 'CONTAINS', 'purchase_approver');
// OR: Manager of requesting department
if (current.requested_for.manager) {
approverQuery.addOrCondition('sys_id', current.requested_for.manager);
}
// AND conditions that always apply
gr.addQuery('active', true);
gr.addQuery('locked_out', false);
// Cost-based filtering for high-value requests
if (current.price > 5000) {
gr.addQuery('roles', 'CONTAINS', 'financial_approver');
}
gr.query();The manager reference check is crucial—always validate that reference fields exist before adding them to OR conditions. Empty reference fields can cause unexpected query behavior or return zero results when you expect matches. Also note how the cost-based role requirement is an additional AND condition, not part of the OR group, which means high-value requests require both department/role eligibility AND financial approval authority.
Change Request Risk Assessment
Change management requires automatic risk flagging for changes that affect critical systems, occur during blackout windows, or involve high-risk categories. The system needs to identify changes requiring additional CAB review based on multiple risk factors.
// Identify high-risk changes requiring CAB review
var riskConditionMet = false;
var gr = new GlideRecord('change_request');
gr.addQuery('sys_id', current.sys_id);
// Risk factor 1: Critical system impact
var riskQuery = gr.addQuery('cmdb_ci.operational_status', '1'); // Operational
riskQuery.addOrCondition('cmdb_ci.business_criticality', 'high');
riskQuery.addOrCondition('category', 'IN', 'hardware,network,security');
// Risk factor 2: Timing-based risk
var startTime = new GlideDateTime(current.start_date);
var dayOfWeek = startTime.getDayOfWeek();
if (dayOfWeek == 1 || dayOfWeek == 7) { // Weekend deployment
riskQuery.addOrCondition('start_date', '!=', '');
}
// Always AND with active change criteria
gr.addQuery('state', 'IN', '-5,-4,-1'); // Requested, Assessment, Authorize
gr.addQuery('type', '!=', 'emergency');
gr.query();
if (gr.hasNext()) {
current.risk = 'high';
current.cab_required = true;
}This pattern shows a common mistake—adding time-based conditions to the OR group when they should probably be separate AND conditions. The weekend check logic needs refinement because it's checking if start_date exists rather than if it falls on a weekend. Consider using GlideDateTime methods to properly evaluate time windows, and remember that OR conditions become unwieldy when mixing different data types—sometimes separate queries are cleaner.
The Classic Mistake
Chaining addOrCondition() directly to the GlideRecord instead of the query object returned by addQuery().
// Business Rule - runs but produces wrong results
var gr = new GlideRecord('incident');
gr.addQuery('state', '1'); // New
gr.addOrCondition('state', '2'); // WRONG - chained to GlideRecord
gr.addQuery('priority', '1'); // Critical
gr.query();
while (gr.next()) {
gs.log('Found incident: ' + gr.number);
// Returns incidents that are (New OR In Progress) AND Critical
// But also returns ALL Critical incidents regardless of state
// Logic becomes: (state=1 OR state=2 OR priority=1)
// Instead of: (state=1 OR state=2) AND priority=1
}When you chain addOrCondition() to the GlideRecord instead of the query object, ServiceNow treats it as an OR against the entire query rather than just the previous condition. You won't see any errors in the browser console or server logs—the query executes successfully but returns far more records than expected. Internally, ServiceNow builds the SQL as a flat series of OR conditions instead of the grouped boolean logic you intended. This silent failure makes it particularly dangerous because it passes testing with small datasets but returns massive result sets in production.
// Business Rule - correct chaining pattern
var gr = new GlideRecord('incident');
var stateQuery = gr.addQuery('state', '1'); // New
stateQuery.addOrCondition('state', '2'); // In Progress - chained to query object
gr.addQuery('priority', '1'); // Critical
gr.query();
while (gr.next()) {
gs.log('Found incident: ' + gr.number);
// Correctly returns incidents that are (New OR In Progress) AND Critical
// Logic: (state=1 OR state=2) AND priority=1
// Much smaller, more targeted result set
}Always store the return value of addQuery() in a variable and chain addOrCondition() to that variable, never to the GlideRecord itself.
Performance Rules
- Never chain more than 5
addOrCondition()calls to a single query object. Beyond 5 OR conditions, query execution time exceeds 10 seconds on large tables likesys_auditorsys_journal_field, triggering system admin timeout alerts. - Always place
setLimit()beforequery()when using OR conditions on non-indexed fields. Without limits, OR queries can return 50,000+ records, causing browser tab crashes and consuming excessive application server memory. - Avoid
addOrCondition()on reference fields without dot-walking to indexed fields. Queries likecaller_id.departmentwith multiple OR conditions force full table scans, degrading instance response time for all users during peak hours. - Use
addQuery('field', 'IN', 'value1,value2,value3')instead of multipleaddOrCondition()calls on the same field. IN clauses execute 3-5x faster and generate cleaner SQL execution plans in the database. - Never use
addOrCondition()withCONTAINSorSTARTSWITHoperators in scheduled jobs. String pattern matching with OR logic bypasses all database indexes, causing 2+ minute query times that trigger job timeout failures. - Add
addQuery('sys_created_on', '>', yesterday)or similar date restrictions before complex OR conditions. Without date bounds, OR queries scan entire table history, consuming gigabytes of buffer pool memory on instances with 10M+ records. - Test OR condition queries with
getRowCount()before iterating results. If count exceeds 10,000 records, refactor the query logic to avoid overwhelming widget rendering or business rule execution times that frustrate end users. - Order OR conditions from most to least selective using
addNullQuery()or highly selective values first. Database query optimizers process OR clauses sequentially—starting with rare conditions allows early query termination and reduces I/O operations.
Side Effects & Platform Behavior
- All Business Rules (Before, After, Async, Display) fire normally for every record returned by OR condition queries—no special handling or reduced rule execution occurs even with large result sets.
- ACL evaluation runs against each individual record in the result set, not against the query conditions themselves—users see only records they have access to regardless of OR logic complexity.
- Query execution details get logged to
sys_db_querytable when queries exceed slow query thresholds, making OR condition performance issues visible to database administrators through Query Performance dashboard. - Domain separation applies to each query condition individually—OR conditions across different domains return only records visible to the current user's domain scope, potentially creating incomplete result sets.
- Client-side GlideRecord with OR conditions bypasses all server-side security controls—use only for reference data lookups, never for sensitive information or user-specific records.
- Audit records in
sys_audittable capture only the final SQL query text, not the individualaddOrCondition()method calls, making query construction debugging more difficult in compliance reviews. - List collectors and report sources using OR conditions automatically refresh every 30 minutes, potentially causing repeated expensive query execution that impacts system performance during business hours.
- OR condition queries break when used inside
GlideAggregategroupBy operations—use encoded query strings withaddEncodedQuery()instead for aggregate reporting with complex boolean logic. - Import set transform maps ignore
addOrCondition()chaining in coalesce field lookups—use comma-separated values withONEOFoperator instead to prevent transform failures and duplicate record creation. - Workflow activities using
addOrCondition()in Run Script activities can trigger workflow context timeouts—the workflow engine kills activities exceeding 60 seconds, leaving workflows in incomplete states visible inwf_contexttable.
Debugging When It Breaks
The most common failure symptom is queries returning far more records than expected—users report seeing "everything" in lists that should be filtered, or widgets load slowly with thousands of unexpected results. You won't see JavaScript errors because the syntax is correct; instead, you'll notice performance degradation and overly broad result sets. Users typically complain that "the search isn't working" or "I'm seeing data I shouldn't see."
For server-side debugging, check System Log > All for slow query warnings or script timeout messages. The Script Debugger shows actual SQL execution when you enable database query logging through System Diagnostics > SQL Debug. Client-side issues appear in browser developer console as long-running network requests or memory warnings when large result sets overwhelm the browser. Look specifically for database connection pool exhaustion messages in sys_log table when multiple users hit OR condition performance problems simultaneously.
Quick diagnostic checklist:
- Check if
getRowCount()returns 10x more records than expected—indicates incorrect OR chaining - Verify the query object variable isn't being reused for multiple
addOrCondition()chains - Log the generated encoded query string using
getEncodedQuery()to see actual boolean logic structure - Test the same query logic manually in a list filter to confirm expected behavior
- Check System Diagnostics > Session Debug for query execution timing over 5 seconds
Quick Reference
- Store
addQuery()return value in variable, chainaddOrCondition()to that variable, never to GlideRecord - Client-side GlideRecord only supports
addOrCondition(field, value)with two parameters—no operator parameter - Use
INoperator with comma-separated values instead of multiple OR conditions on same field for better performance - Cannot combine
addOrCondition()withchooseWindow()for pagination—use encoded queries instead - OR conditions don't work with
deleteMultiple()—method ignores chained conditions and deletes based on primary query only - Each query object can only be used once—calling
query()resets all chained OR conditions for subsequent calls - Reference field OR conditions like
caller_id.departmentrequire exact sys_id values, not display values - Business rule
currentobject queries with OR conditions can modify the triggering record's field values unintentionally - Scoped applications must use
x_scope_name_table_nameformat in OR condition field references, not short table names - Date/time fields in OR conditions use GMT internally—convert user timezone values with
gs.dateGenerate()for accurate comparisons