ServiceNow
GlideRecord Complete Reference: Every Method You Actually Use in Production
What GlideRecord is
GlideRecord is the server-side JavaScript API for reading and writing database records in ServiceNow. Every Business Rule, Script Include, Scheduled Job, and Fix Script you write will use it. Think of it as a cursor-based query builder — you build a query, execute it, then iterate row by row.
Query (SELECT)
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.addQuery('state', '!=', 6); // 6 = Resolved
gr.addQuery('assignment_group.name', 'Network Ops'); // dot-walk through reference
gr.orderByDesc('sys_created_on');
gr.setLimit(20);
gr.query();
while (gr.next()) {
gs.info(gr.getValue('number')); // INC0010045
gs.info(gr.getDisplayValue('caller_id')); // John Smith
gs.info(gr.getValue('caller_id')); // 6816f79cc0a8016401... (sys_id)
}
What actually happens: query() sends SQL to the database. next() moves the cursor to the next row. When there are no more rows, next() returns false and the loop ends.
addQuery operators
| Operator | Example | What it does |
|---|---|---|
| = | addQuery('priority', 1) | Exact match (default) |
| != | addQuery('state', '!=', 6) | Not equal |
| > | addQuery('sys_created_on', '>', '2026-01-01') | Greater than |
| < | addQuery('priority', '<', 3) | Less than |
| IN | addQuery('state', 'IN', '1,2,3') | Value in comma-separated list |
| NOT IN | addQuery('state', 'NOT IN', '6,7') | Value not in list |
| STARTSWITH | addQuery('number', 'STARTSWITH', 'INC') | String starts with |
| CONTAINS | addQuery('short_description', 'CONTAINS', 'VPN') | String contains |
| ISEMPTY / null | addNullQuery('assigned_to') | Field is null/empty (prefer addNullQuery in script) |
| ISNOTEMPTY | addNotNullQuery('assigned_to') | Field has a value |
OR queries
var gr = new GlideRecord('incident');
var q1 = gr.addQuery('priority', 1);
q1.addOrCondition('priority', 2); // priority = 1 OR priority = 2
gr.query();
Encoded queries
Instead of chaining addQuery calls, paste the encoded query string directly. Copy it from any list filter in the UI — click the filter icon, set your conditions, right-click the breadcrumb, and select "Copy query."
var gr = new GlideRecord('incident');
gr.addEncodedQuery('priority=1^stateNOT IN6,7^assignment_group.name=Network Ops');
gr.query();
// This does the same thing as the three addQuery calls above.
// Encoded queries are faster to write and easier to paste from the UI.
Get a single record
// By sys_id
var gr = new GlideRecord('incident');
gr.get('a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6');
// By field value
var gr = new GlideRecord('incident');
gr.get('number', 'INC0010001');
// gr.get() returns true/false. No need to call query() or next().
Insert
var gr = new GlideRecord('incident');
gr.initialize();
gr.short_description = 'Printer offline on floor 3';
gr.priority = 3;
gr.assignment_group.setDisplayValue('Service Desk'); // look up by name
var newSysId = gr.insert(); // returns the sys_id of the new record
gs.info('Created: ' + newSysId);
Update
var gr = new GlideRecord('incident');
gr.get('number', 'INC0010001');
gr.state = 6; // Resolved
gr.close_notes = 'Restarted the print spooler service.';
gr.update();
Bulk update
// Close all resolved incidents older than 7 days
var gr = new GlideRecord('incident');
gr.addQuery('state', 6);
gr.addQuery('resolved_at', '<', gs.daysAgo(7));
gr.query();
while (gr.next()) {
gr.state = 7; // Closed
gr.update();
}
// For large volumes, use gr.setWorkflow(false) to skip Business Rules
// and gr.autoSysFields(false) to skip sys_updated_on/by changes.
Delete
var gr = new GlideRecord('incident');
gr.get('number', 'INC9999999');
gr.deleteRecord();
// Bulk delete — be very careful
var gr = new GlideRecord('u_temp_import');
gr.addQuery('sys_created_on', '<', gs.daysAgo(30));
gr.deleteMultiple(); // Deletes all matching records without loading them
getValue vs getDisplayValue vs dot notation
| Method | Returns | Use when |
|---|---|---|
gr.getValue('caller_id') | sys_id string | Comparisons, integrations, storing references |
gr.getDisplayValue('caller_id') | Display name | Messages, emails, UI display |
gr.caller_id | GlideElement object | Avoid — behaves unpredictably in comparisons |
gr.getValue('state') | '1' (internal number) | Logic, conditions |
gr.getDisplayValue('state') | 'New' (label) | Display only — labels change with language |
GlideAggregate
When you need COUNT, SUM, MIN, MAX, or AVG — do not loop through GlideRecord and count manually.
// Count open P1 incidents
var ga = new GlideAggregate('incident');
ga.addQuery('priority', 1);
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.query();
if (ga.next()) {
gs.info('Open P1 count: ' + ga.getAggregate('COUNT'));
}
// Count per assignment group
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.groupBy('assignment_group');
ga.query();
while (ga.next()) {
gs.info(ga.getDisplayValue('assignment_group') + ': ' + ga.getAggregate('COUNT'));
}
Common traps
- Querying the task table:
new GlideRecord('task')returns incidents + changes + problems + catalog tasks + everything else that extends task. Always query the specific child table unless you genuinely want all task types. - getRowCount(): Runs a full
COUNT(*)against the database. On tables with millions of rows, this is expensive. UseGlideAggregateinstead. - Dot-walking in loops:
gr.caller_id.department.namefires a separate query per iteration. For bulk operations, query the related table separately and build a lookup map. - Missing setLimit(): Without a limit, your query can return hundreds of thousands of rows. Always set a limit unless you specifically need all records.
- gr.field = value vs gr.setValue('field', value): Both work for most fields, but
setValueis safer for reference fields and journal fields. UsesetValuein script includes; direct assignment in business rules is fine.