GlideRecord Best Practices and Performance
Introduction
GlideRecord is the foundation of all server-side data interaction in ServiceNow. Every Business Rule, Script Include, Flow Action, and scheduled script uses it. Yet many developers use only a fraction of its API and unknowingly write queries that perform fine in development but degrade under production load.
This guide covers the GlideRecord API from basic to advanced, with a strong focus on patterns that maintain performance at scale.
The GlideRecord Lifecycle
new GlideRecord(table)
↓
addQuery() / addEncodedQuery() / addOrCondition()
↓
query()
↓
while(next())
↓
getValue() / setValue() / getDisplayValue() / update() / deleteRecord()
Query Methods Deep Dive
addQuery() — Standard Filtering
var gr = new GlideRecord('incident');
// Equals
gr.addQuery('state', '1'); // 1 = New
// Operators: CONTAINS, STARTSWITH, ENDSWITH, IN, NOT IN, INSTANCEOF, ISEMPTY, ISNOTEMPTY
gr.addQuery('short_description', 'CONTAINS', 'login');
gr.addQuery('priority', 'IN', '1,2'); // Multiple values
gr.addQuery('assigned_to', 'ISNOTEMPTY', '');
gr.addQuery('sys_created_on', '>=', '2024-01-01 00:00:00');
// AND logic: multiple addQuery calls are ANDed together
gr.addQuery('state', '1');
gr.addQuery('priority', '1'); // State=New AND Priority=1
gr.query();
addOrCondition() — OR Logic
var gr = new GlideRecord('incident');
var qc = gr.addQuery('state', '1'); // New
qc.addOrCondition('state', '2'); // OR In Progress
qc.addOrCondition('state', '3'); // OR On Hold
gr.addQuery('priority', '1'); // AND Priority = 1
gr.query();
// Result: Priority=1 AND (State=New OR State=In Progress OR State=On Hold)
addEncodedQuery() — The Power Move
Encoded queries let you use the full power of ServiceNow's query string format:
var gr = new GlideRecord('incident');
// Copy encoded query from any list view's URL or condition builder
gr.addEncodedQuery(
'active=true^priority=1^stateIN1,2,3^' +
'sys_created_onONLast 7 days@javascript:gs.beginningOfLast7Days()@' +
'javascript:gs.endOfLast7Days()'
);
gr.orderByDesc('sys_created_on');
gr.setLimit(100);
gr.query();
Getting and Setting Values Correctly
This is where most developers make subtle errors:
var gr = new GlideRecord('incident');
gr.get('number', 'INC0010001');
// getValue() — Returns raw database value (always a string)
var stateCode = gr.getValue('state'); // Returns "1", "2", etc.
// getDisplayValue() — Returns human-readable display value
var stateLabel = gr.getDisplayValue('state'); // Returns "New", "In Progress"
// Directly accessing field objects (returns GlideElement)
var priorityElement = gr.priority; // GlideElement object
var priorityValue = gr.priority.toString(); // Equivalent to getValue
var priorityDisplay = gr.priority.getDisplayValue();
// Reference fields:
var assigneeEmail = gr.assigned_to.email.toString(); // Dot-walk
var assigneeName = gr.getDisplayValue('assigned_to'); // Display of reference
var assigneeSysId = gr.getValue('assigned_to'); // Sys ID of referenced record
// Setting values:
gr.setValue('state', '2'); // Set by value
gr.setValue('priority', '1');
gr.assignment_group.setDisplayValue('Service Desk'); // Set by display value
gr.update();
Performance Patterns
1. Use setLimit() for Large Tables
// ❌ Queries all matching records — dangerous on large tables
var gr = new GlideRecord('sys_audit');
gr.addQuery('tablename', 'incident');
gr.query();
// ✅ Limit your result set
var gr = new GlideRecord('sys_audit');
gr.addQuery('tablename', 'incident');
gr.orderByDesc('sys_created_on');
gr.setLimit(500);
gr.query();
2. Use getAggregate() Instead of Iterating for Counts
// ❌ Slow: Loads all records just to count them
var count = 0;
var gr = new GlideRecord('incident');
gr.addQuery('state', '1');
gr.query();
while (gr.next()) count++;
// ✅ Fast: Database-level aggregation
var ga = new GlideAggregate('incident');
ga.addQuery('state', '1');
ga.addAggregate('COUNT');
ga.query();
if (ga.next()) {
var count = ga.getAggregate('COUNT');
}
3. Choose Aggregate Functions Wisely
var ga = new GlideAggregate('incident');
ga.addQuery('state', '1');
ga.addAggregate('COUNT'); // Total records
ga.addAggregate('COUNT', 'priority'); // Count by priority
ga.addAggregate('AVG', 'time_worked'); // Average time
ga.addAggregate('SUM', 'time_worked'); // Total time
ga.addAggregate('MIN', 'sys_created_on'); // Oldest
ga.addAggregate('MAX', 'sys_created_on'); // Newest
ga.groupBy('priority'); // Group results by priority
ga.query();
while (ga.next()) {
gs.info('P' + ga.getValue('priority') +
': ' + ga.getAggregate('COUNT') + ' incidents');
}
4. Bulk Operations with updateMultiple()
// ❌ Slow: Fires Business Rules for every record
var gr = new GlideRecord('incident');
gr.addQuery('state', '1');
gr.addQuery('sys_created_on', '<',
gs.daysAgoStart(30));
gr.query();
while (gr.next()) {
gr.setValue('state', '7'); // Closed
gr.update(); // Fires BR for each record
}
// ✅ Fast: Single database operation (use carefully — skips BRs)
var gr = new GlideRecord('incident');
gr.addQuery('state', '1');
gr.addQuery('sys_created_on', '<',
gs.daysAgoStart(30));
gr.setValue('state', '7');
gr.updateMultiple();
// WARNING: updateMultiple() bypasses Business Rules, ACLs, and audit
// Only use when you explicitly want to skip those
GlideDateTime Operations
// Get current time
var now = new GlideDateTime();
// Add/subtract time
var future = new GlideDateTime();
future.addSeconds(3600); // Add 1 hour
future.addDays(5); // Add 5 days
// Compare datetimes
var created = current.sys_created_on;
var now = new GlideDateTime();
if (GlideDateTime.subtract(now, created).getDayPart() > 7) {
gs.info('Record is older than 7 days');
}
// Format for display
gs.info(now.getDisplayValue()); // User locale
gs.info(now.getValue()); // Database format: YYYY-MM-DD HH:MM:SS
// Relative date queries
gr.addQuery('sys_created_on', '>',
gs.daysAgo(7));
gr.addQuery('sys_updated_on', '>',
gs.beginningOfThisWeek());
deleteMultiple() — Use with Extreme Caution
// Bulk delete — IRREVERSIBLE, skips BRs and audit
// Only use after thorough testing
var gr = new GlideRecord('import_set_row');
gr.addQuery('state', 'complete');
gr.addQuery('sys_created_on', '<', gs.daysAgoStart(90));
gr.deleteMultiple();
// Always test with query() and log record counts before deleting!
Common GlideRecord Mistakes
| Mistake | Impact | Fix |
|---|---|---|
gr.value instead of gr.getValue('value') |
Returns GlideElement object, not string | Always use getValue() |
Not checking gr.get() return value |
Null pointer on failed lookups | if (gr.get(id)) { ... } |
| Query inside a loop (N+1) | Exponential performance degradation | Pre-fetch data before the loop |
No setLimit() on large tables |
Out of memory / timeout | Always limit exploratory queries |
update() inside an after BR |
Potential infinite loop | Use before BR or set a condition flag |
Conclusion
GlideRecord mastery is the single most impactful skill for ServiceNow server-side performance. The difference between a developer who knows addQuery() and one who knows addOrCondition(), getAggregate(), updateMultiple(), and proper GlideDateTime handling is the difference between an instance that scales and one that degrades. Practice these patterns until they're instinctive.