‹ All essays

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

OperatorExampleWhat 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
INaddQuery('state', 'IN', '1,2,3')Value in comma-separated list
NOT INaddQuery('state', 'NOT IN', '6,7')Value not in list
STARTSWITHaddQuery('number', 'STARTSWITH', 'INC')String starts with
CONTAINSaddQuery('short_description', 'CONTAINS', 'VPN')String contains
ISEMPTY / nulladdNullQuery('assigned_to')Field is null/empty (prefer addNullQuery in script)
ISNOTEMPTYaddNotNullQuery('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

MethodReturnsUse when
gr.getValue('caller_id')sys_id stringComparisons, integrations, storing references
gr.getDisplayValue('caller_id')Display nameMessages, emails, UI display
gr.caller_idGlideElement objectAvoid — 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. Use GlideAggregate instead.
  • Dot-walking in loops: gr.caller_id.department.name fires 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 setValue is safer for reference fields and journal fields. Use setValue in script includes; direct assignment in business rules is fine.