
ServiceNow GlideRecord Explained: Queries, Examples & Best Practices
GlideRecord is one of the most important APIs to understand when developing on the ServiceNow platform.
If you're writing server-side JavaScript, sooner or later you'll need to:
- find records
- filter records
- read field values
- follow references
- update records
- create records
- delete records
- count records
- sort results
- limit results
GlideRecord gives us the API for working with ServiceNow tables through JavaScript.
A basic query can be only a few lines:
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.query()10
11while (incidentGR.next()) {12 gs.info(13 incidentGR.getValue(14 'number',15 ),16 )17}The syntax is simple.
The important part is learning how to use GlideRecord safely and efficiently.
A poorly designed query can scan far more records than necessary, perform repeated database operations, or make server transactions much slower than they need to be.
In this guide, we'll work through the main GlideRecord patterns and the practices that make them easier to maintain in real ServiceNow applications.
What Is GlideRecord?
GlideRecord is a ServiceNow API for interacting with records in a table.
When we write:
1var incidentGR =2 new GlideRecord('incident')we create a GlideRecord object representing the incident table.
At this point, no database query has been executed.
We still need to define what records we want and then call:
1incidentGR.query()After the query executes, we can move through the results using:
1incidentGR.next()That gives us the standard GlideRecord pattern:
- create the GlideRecord
- add conditions
- execute the query
- iterate through the results
- read or modify records
The Basic GlideRecord Query
Let's find active incidents.
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.query()10
11while (incidentGR.next()) {12 gs.info(13 incidentGR.getValue(14 'number',15 ),16 )17}The important pieces are:
new GlideRecord('incident')
Selects the table.
addQuery('active', true)
Adds a filter.
query()
Executes the database query.
next()
Moves to the next matching record.
next() Returns Whether Another Record Exists
The pattern:
1while (incidentGR.next()) {2 // process record3}continues until no more matching records exist.
If the query finds five records, the loop runs five times.
Inside each iteration, incidentGR represents the current record.
We can then access its fields.
Use Meaningful GlideRecord Variable Names
You will often see code like:
1var gr =2 new GlideRecord('incident')That works, but larger scripts quickly become difficult to read when several GlideRecords are all called gr, gr1, and gr2.
Prefer names that describe the table or responsibility.
For example:
1var incidentGR =2 new GlideRecord('incident')or:
1var userGR =2 new GlideRecord('sys_user')or:
1var taskGR =2 new GlideRecord('task')This becomes especially useful when multiple tables are involved.
Using addQuery()
The most common way to filter GlideRecord results is:
1addQuery()For example:
1incidentGR.addQuery(2 'priority',3 '1',4)This asks for records where Priority equals 1.
You can also provide an operator.
For example:
1incidentGR.addQuery(2 'sys_created_on',3 '>=',4 '2026-01-01 00:00:00',5)The general pattern is:
1addQuery(2 field,3 operator,4 value,5)When the operator is omitted, equality is assumed.
Multiple addQuery() Calls Are AND Conditions
Suppose we write:
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.addQuery(10 'priority',11 '1',12)13
14incidentGR.query()This means:
Active is true AND Priority is 1.
Both conditions must match.
This is one of the most common GlideRecord patterns.
Common Query Operators
GlideRecord supports many query operators.
Common examples include:
=!=><>=<=INNOT INLIKESTARTSWITHENDSWITHCONTAINS
For example:
1incidentGR.addQuery(2 'number',3 'STARTSWITH',4 'INC',5)Or:
1incidentGR.addQuery(2 'priority',3 'IN',4 '1,2',5)The second query returns records whose Priority is either 1 or 2.
Use the Simplest Query That Expresses the Requirement
If you only need equality:
1incidentGR.addQuery(2 'active',3 true,4)is clearer than adding unnecessary complexity.
Queries are easier to maintain when someone can immediately understand what is being filtered.
Adding OR Conditions
Sometimes we need:
Priority is 1 OR Priority is 2.
One way is using IN:
1incidentGR.addQuery(2 'priority',3 'IN',4 '1,2',5)For more complex OR conditions, addQuery() returns a query condition that can be extended.
For example:
1var incidentGR =2 new GlideRecord('incident')3
4var priorityCondition =5 incidentGR.addQuery(6 'priority',7 '1',8 )9
10priorityCondition.addOrCondition(11 'priority',12 '2',13)14
15incidentGR.query()16
17while (incidentGR.next()) {18 gs.info(19 incidentGR.getValue(20 'number',21 ),22 )23}This produces an OR condition between those priority values.
Be Careful With Complex AND/OR Logic
Once queries contain several combinations of AND and OR conditions, they become harder to read correctly in script.
For complicated filters, encoded queries can sometimes make the condition easier to transfer from ServiceNow's filter builder.
But encoded queries have their own trade-offs.
Encoded Queries
ServiceNow filters can be represented as encoded query strings.
For example:
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addEncodedQuery(5 'active=true^priority=1'6)7
8incidentGR.query()The caret represents another condition.
This means:
Active is true AND Priority is 1.
Encoded queries are especially useful for complicated conditions created through the platform's filter interface.
Build Complicated Filters in the UI First
One practical technique is:
- open the table list
- create the filter using ServiceNow's condition builder
- verify that the results are correct
- copy the encoded query
- use that query in your script
This can reduce mistakes when writing complicated conditions manually.
However, you should still understand what the encoded query means.
Do not paste a large encoded query into production code without understanding the conditions it contains.
Be Careful With Invalid Query Fields
A typo in a field name can be more dangerous than it looks.
Imagine writing a query against a field that does not exist.
Depending on query behavior and configuration, the invalid portion may not restrict the result set the way you expect.
That can lead to processing far more records than intended.
For important queries:
- verify field names
- test the filter
- test in sub-production first
- add reasonable limits during development
Never assume a query is safe simply because it does not throw an obvious JavaScript error.
Use get() When You Need One Specific Record
If you already know the sys_id, you often do not need a full query loop.
Use:
1var incidentGR =2 new GlideRecord('incident')3
4if (5 incidentGR.get(6 'YOUR_SYS_ID'7 )8) {9 gs.info(10 incidentGR.getValue(11 'number',12 ),13 )14}get() returns true if the record was found.
This is much clearer than:
1addQuery('sys_id', ...)2query()3if (next())when the goal is simply retrieving one known record.
get() Can Query Another Field Too
GlideRecord can also retrieve a record using a field and value.
For example:
1var incidentGR =2 new GlideRecord('incident')3
4if (5 incidentGR.get(6 'number',7 'INC0010001',8 )9) {10 gs.info(11 incidentGR.getValue(12 'short_description',13 ),14 )15}Be careful when using a field that is not guaranteed to be unique.
If the requirement expects exactly one record, your data model should support that assumption.
Reading Field Values
Once GlideRecord is positioned on a record, we can retrieve values.
A common approach is:
1incidentGR.getValue(2 'short_description',3)For example:
1var number =2 incidentGR.getValue(3 'number',4 )5
6var description =7 incidentGR.getValue(8 'short_description',9 )Using explicit getter methods often makes it clear that we're retrieving the underlying stored value.
Database Value vs Display Value
ServiceNow fields can have both:
- an underlying value
- a display value
This is especially important for:
- reference fields
- choice fields
- dates
For example, a reference field may store a sys_id but display a person's name.
Using:
1incidentGR.getValue(2 'caller_id',3)returns the underlying value.
For a reference field, that is normally the referenced record's sys_id.
If we want the value shown to the user, we can use the field's display value.
For example:
1var callerName =2 incidentGR3 .caller_id4 .getDisplayValue()Now we get the display value of the caller rather than the raw sys_id.
Why getValue() Is Useful
Consider:
1var callerId =2 incidentGR.getValue(3 'caller_id',4 )This gives us a string value that can safely be:
- compared
- logged
- stored
- passed to another function
It also makes our intention explicit.
Use Display Values for Presentation
If the result is intended for a human, the display value is often more useful.
For example:
1gs.info(2 'Caller: ' +3 incidentGR4 .caller_id5 .getDisplayValue()6)Rather than logging a 32-character sys_id, we see the caller's display name.
The correct choice depends on whether the code needs the actual stored value or the human-readable value.
Reference Fields Need Special Attention
Reference fields connect one table to another.
For example, caller_id on an Incident references sys_user.
The stored value is normally the user's sys_id.
The displayed value might be:
Abel Tuter
Those are two different values.
This distinction matters when:
- comparing references
- setting reference fields
- logging information
- displaying results
- passing IDs between systems
Avoid Unnecessary Deep Dot-Walking
ServiceNow lets us write things such as:
1incidentGR.caller_id.manager.emailThis can be convenient.
But excessive dot-walking can make scripts harder to reason about and may retrieve more referenced data than necessary.
If you need information from a referenced record repeatedly, consider retrieving that record clearly.
For example:
1var callerGR =2 incidentGR3 .caller_id4 .getRefRecord()5
6if (callerGR.isValidRecord()) {7 var email =8 callerGR.getValue(9 'email',10 )11}Now it is clear that we're working with the referenced user record.
Don't Query the Same Reference Repeatedly
Imagine processing 500 incidents and querying the caller manually inside every loop iteration.
That can result in hundreds of additional database queries.
Before querying a referenced table inside a loop, ask:
- do I already have the value?
- can I use the reference field directly?
- can the query be redesigned?
- am I repeatedly retrieving the same record?
Database calls inside loops are one of the first things to inspect when server-side code becomes slow.
Ordering Results
You can sort GlideRecord results using:
1orderBy()For example:
1incidentGR.orderBy(2 'number',3)For descending order:
1incidentGR.orderByDesc(2 'sys_created_on',3)A common use case is retrieving the newest records first.
Limiting Results
If you only need a small number of records, use:
1setLimit()For example:
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.orderByDesc(10 'sys_created_on',11)12
13incidentGR.setLimit(10)14
15incidentGR.query()16
17while (incidentGR.next()) {18 gs.info(19 incidentGR.getValue(20 'number',21 ),22 )23}This asks for at most ten matching records.
Do not retrieve 100,000 records when the requirement only needs the newest ten.
Query Only What You Need
A very important GlideRecord habit is:
Make the database narrow the result set before your JavaScript loop begins.
Avoid querying every Incident and then doing this:
1while (incidentGR.next()) {2 if (3 incidentGR.getValue(4 'priority',5 ) === '1'6 ) {7 // process8 }9}when you could simply add:
1incidentGR.addQuery(2 'priority',3 '1',4)Let the query filter records.
Do not use JavaScript loops as a replacement for database filtering.
Check Whether a Record Exists
If all you need to know is whether at least one record matches, you do not need to process every result.
For example:
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.addQuery(10 'priority',11 '1',12)13
14incidentGR.setLimit(1)15
16incidentGR.query()17
18if (incidentGR.next()) {19 gs.info(20 'At least one active critical incident exists.'21 )22}The requirement only needs existence, so there is no reason to retrieve a large result set.
Updating a Record
Once GlideRecord is positioned on a record, fields can be changed and the record updated.
For example:
1var incidentGR =2 new GlideRecord('incident')3
4if (5 incidentGR.get(6 'YOUR_SYS_ID'7 )8) {9 incidentGR.setValue(10 'short_description',11 'Updated description',12 )13
14 incidentGR.update()15}update() writes the changes to the database.
setValue() Makes Dynamic Field Updates Clear
You can often assign directly:
1incidentGR.priority = '1'But setValue() is useful when:
- the field name is stored in a variable
- you want explicit field-setting code
- you're writing reusable logic
For example:
1var fieldName =2 'priority'3
4incidentGR.setValue(5 fieldName,6 '1',7)Whichever style you use, keep it consistent and make sure the value matches the field's expected data type.
Updating Multiple Records
Sometimes we genuinely need to iterate through matching records and update each one.
For example:
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.addQuery(10 'u_legacy_flag',11 true,12)13
14incidentGR.query()15
16while (incidentGR.next()) {17 incidentGR.setValue(18 'u_legacy_flag',19 false,20 )21
22 incidentGR.update()23}This performs an update for every matching record.
Before using this pattern against large tables, consider:
- how many records will match
- what Business Rules will run
- what flows may trigger
- whether audits will be generated
- whether another bulk-operation approach is more appropriate
A few records and hundreds of thousands of records are very different operations.
Be Especially Careful With Bulk Updates
A script that updates a large number of records can trigger:
- Business Rules
- Flow Designer flows
- notifications
- audits
- events
- integrations
The database update itself may only be part of the cost.
Always test bulk operations in sub-production and understand what automation exists on the table.
GlideRecord Inside Business Rules
GlideRecord is frequently used inside Business Rules, but be careful when querying or updating the same table that triggered the rule.
For example, calling:
1current.update()inside a Business Rule on the current record is often unnecessary and can cause additional rule execution.
If you're inside a Before Business Rule and simply need to change the current record, modify current and allow the existing transaction to save it.
Use additional GlideRecord operations only when the requirement genuinely needs them.
Creating a New Record
GlideRecord can also insert records.
A common pattern is:
1var taskGR =2 new GlideRecord(3 'incident_task',4 )5
6taskGR.initialize()7
8taskGR.setValue(9 'short_description',10 'Follow-up task',11)12
13taskGR.setValue(14 'incident',15 current.getUniqueValue(),16)17
18var newTaskSysId =19 taskGR.insert()initialize() prepares a new record.
We set the required values.
Then:
1insert()creates it.
The returned value can be used to identify the new record.
Make Sure Required Data Exists Before Insert
Do not assume an insert succeeded just because the script reached:
1insert()Make sure:
- required fields are populated
- reference values are valid
- Business Rules will not reject the operation
- your script handles the expected result
If record creation is critical, log or handle failures appropriately.
Deleting Records
GlideRecord can delete a record using:
1deleteRecord()For example:
1var tempGR =2 new GlideRecord(3 'u_temp_record',4 )5
6if (7 tempGR.get(8 'YOUR_SYS_ID'9 )10) {11 tempGR.deleteRecord()12}Deletion should be treated carefully.
A bad update may be reversible.
A bad delete may not be.
For deletion scripts:
- make the filter extremely specific
- test in sub-production
- confirm the expected record count
- consider whether records should be deactivated rather than deleted
- understand related-record behavior
Never Start a Destructive Script With a Broad Query
This is dangerous:
1var recordGR =2 new GlideRecord(3 'some_table',4 )5
6recordGR.query()7
8while (recordGR.next()) {9 recordGR.deleteRecord()10}You have effectively asked ServiceNow to delete every record returned from the table.
When testing destructive operations, begin with:
- a specific query
- a small limit
- logging instead of deletion
Only perform the real operation after verifying the exact records that will be affected.
Count Records With GlideAggregate
Sometimes developers write a GlideRecord query only because they need to know:
How many records match?
For that requirement, GlideAggregate is usually the better tool.
For example:
1var incidentGA =2 new GlideAggregate(3 'incident',4 )5
6incidentGA.addQuery(7 'active',8 true,9)10
11incidentGA.addAggregate(12 'COUNT',13)14
15incidentGA.query()16
17if (incidentGA.next()) {18 var count =19 incidentGA.getAggregate(20 'COUNT',21 )22
23 gs.info(24 'Active incidents: ' +25 count26 )27}This asks the database for an aggregate count instead of retrieving every matching record just to count them afterward.
GlideAggregate Can Do More Than Count
GlideAggregate supports operations such as:
- COUNT
- SUM
- MIN
- MAX
- AVG
For example, if the requirement is a calculation across a group of records, consider whether GlideAggregate expresses the problem more directly than GlideRecord.
Use GlideRecord when you need the records.
Use GlideAggregate when you primarily need an aggregate result.
Don't Use getRowCount() Just to Count a Huge Result Set
GlideRecord provides:
1getRowCount()But if the only requirement is counting records, retrieving the record set merely to count it is usually unnecessary.
Prefer GlideAggregate for count-only requirements.
If you're already retrieving and processing the records for another reason, the trade-off is different.
The key is to avoid performing work the requirement does not need.
Avoid Queries Inside Loops
One of the most common GlideRecord performance problems looks like this:
1while (incidentGR.next()) {2 var userGR =3 new GlideRecord(4 'sys_user',5 )6
7 userGR.get(8 incidentGR.getValue(9 'caller_id',10 )11 )12
13 // process user14}If the incident query returns 1,000 records, this could create up to 1,000 additional record lookups.
Sometimes that is unavoidable.
Often it is not.
Before querying inside a loop, look for ways to:
- use reference values already available
- collect IDs and query once
- redesign the query
- cache repeated lookups
- move repeated logic outside the loop
Query Once When Possible
Suppose you need users for many records.
Instead of querying each user individually, you may be able to collect the required user IDs and perform one query using IN.
The exact implementation depends on the requirement, but the general principle is:
Fewer well-designed database queries are usually preferable to many tiny repeated queries.
Use setLimit() During Development
While testing a new Background Script, especially against a large table, a limit can reduce risk.
For example:
1incidentGR.setLimit(10)Then inspect those results before removing or increasing the limit.
This is particularly helpful when learning how a query behaves.
Log Before You Modify
For potentially risky scripts, start by logging what the script would change.
For example:
1while (incidentGR.next()) {2 gs.info(3 'Would update: ' +4 incidentGR.getValue(5 'number',6 )7 )8}Once the output is correct, introduce the actual update.
This simple practice can prevent painful mistakes.
Test Queries in Sub-Production
A query that looks correct may behave differently from what you expected.
Before deploying significant GlideRecord logic to production:
- test in development
- verify the filter manually
- inspect the number of matching records
- test edge cases
- understand triggered automation
- review performance
This is especially important for scripts that update or delete records.
Be Aware of Table Size
Querying a custom table with 100 records is very different from querying a heavily used task table with millions of records.
As tables grow, query quality matters more.
Consider:
- how selective the conditions are
- whether suitable indexes exist
- how frequently the query runs
- whether the query is inside a synchronous transaction
- how many records you actually need
A query that is harmless in a developer instance may become expensive at enterprise scale.
Use Specific Conditions
Compare:
1var taskGR =2 new GlideRecord('task')3
4taskGR.query()with:
1var taskGR =2 new GlideRecord('task')3
4taskGR.addQuery(5 'active',6 true,7)8
9taskGR.addQuery(10 'assignment_group',11 groupSysId,12)13
14taskGR.addQuery(15 'sys_created_on',16 '>=',17 startDate,18)19
20taskGR.query()The second query communicates exactly what records are required.
Broad queries should be a deliberate choice, not the default.
Indexes Matter
ServiceNow tables use database indexes to improve common query patterns.
When a heavily used query filters large tables, index design may become relevant.
That does not mean creating an index for every script.
Indexes also have a cost.
But if a frequently executed query performs poorly against a large table, investigate whether the query conditions align with appropriate indexes.
Performance problems should be measured rather than guessed.
Don't Query Data You Already Have
Inside a Business Rule, you already have:
1currentIf the value you need is already on current, do not create another GlideRecord query just to retrieve the same record.
For example, avoid:
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.get(5 current.getUniqueValue()6)just to read:
1incidentGR.getValue(2 'priority',3)when:
1current.getValue(2 'priority',3)already provides the value.
This removes an unnecessary database operation.
Use isValidRecord() When Appropriate
When working with a GlideRecord that may or may not represent a valid record, you can check:
1if (2 recordGR.isValidRecord()3) {4 // safe to continue5}This can be useful after reference navigation or other operations where the existence of a valid record is not guaranteed.
Do not assume references always contain valid data.
Check for Empty Values Clearly
ServiceNow provides several ways to check values.
Depending on the context, you might use:
1gs.nil(2 incidentGR.getValue(3 'assignment_group',4 )5)or a direct check against the retrieved value.
The important thing is to handle empty or missing data intentionally.
Reference fields, optional fields, and imported records may not always contain the values your script expects.
Reusable Queries Belong in Reusable Server Logic
If the same GlideRecord logic appears repeatedly in:
- Business Rules
- Scheduled Jobs
- Scripted REST APIs
- Background Scripts
consider moving it into a Script Include.
For example:
1var IncidentUtils =2 Class.create()3
4IncidentUtils.prototype = {5 initialize:6 function() {},7
8 getCriticalIncidents:9 function() {10 var incidents = []11
12 var incidentGR =13 new GlideRecord(14 'incident',15 )16
17 incidentGR.addQuery(18 'active',19 true,20 )21
22 incidentGR.addQuery(23 'priority',24 '1',25 )26
27 incidentGR.query()28
29 while (30 incidentGR.next()31 ) {32 incidents.push(33 incidentGR34 .getUniqueValue()35 )36 }37
38 return incidents39 },40
41 type:42 'IncidentUtils',43}Now the query has one maintained implementation.
Keep Script Includes Focused Too
Moving a bad query into a Script Include does not automatically make it good architecture.
The Script Include should still have:
- a clear responsibility
- focused queries
- meaningful method names
- predictable return values
Reusable logic should be easier to understand than duplicated logic.
GlideRecord and Access Control
Database access and user access are not always the same concern.
Server-side scripts can execute in contexts where ordinary GlideRecord operations should not be treated as equivalent to a user's UI permissions.
When a query must explicitly respect user-level access, use the appropriate security-aware APIs and patterns for your ServiceNow release and application context.
Depending on the requirement, that may include mechanisms such as:
GlideRecordSecure- ACL-aware query methods
- explicit access checks
Do not assume that hiding something in the UI automatically protects the corresponding server-side data.
Security should be designed intentionally.
GlideRecordSecure
GlideRecordSecure provides a GlideRecord-style interface designed around security checks.
For example:
1var incidentGR =2 new GlideRecordSecure(3 'incident',4 )5
6incidentGR.addQuery(7 'active',8 true,9)10
11incidentGR.query()12
13while (incidentGR.next()) {14 // process records the15 // security context allows16}Whether this is the right API depends on what the script is doing.
The important principle is:
Choose the data-access method based on the security requirements of the operation.
Don't Confuse GlideRecord With ACLs
GlideRecord is a data-access API.
ACLs are part of ServiceNow's access-control model.
They solve related but different problems.
When building an application, consider:
- who should access the record
- who should access particular fields
- which code runs with which authority
- whether user-provided query input is involved
Do not use query filters as a substitute for proper access control.
What About Client-Side GlideRecord?
ServiceNow also has client-side GlideRecord capabilities, but you should not automatically use them whenever a Client Script needs data.
For form logic that requires server-side information, consider whether GlideAjax and a client-callable Script Include provide a cleaner boundary.
Client-server queries have:
- network latency
- security considerations
- browser impact
If client-side querying is used, asynchronous patterns are important.
For learning GlideRecord architecture, server-side usage is the best place to begin.
Never Make the Browser Wait on an Unnecessary Query
A synchronous client-side database request can make the interface wait while the server responds.
That creates a poor user experience.
For client-side requirements:
- determine whether the data is already available
- consider declarative platform features
- use asynchronous server communication where necessary
Do not treat browser-side GlideRecord as a replacement for thoughtful client/server architecture.
Example: Find Active Critical Incidents
Here is a clean server-side query:
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.addQuery(10 'priority',11 '1',12)13
14incidentGR.orderByDesc(15 'sys_created_on',16)17
18incidentGR.query()19
20while (incidentGR.next()) {21 gs.info(22 incidentGR.getValue(23 'number',24 ) +25 ' - ' +26 incidentGR.getValue(27 'short_description',28 )29 )30}The query clearly communicates its intention.
Example: Find the Most Recent Matching Record
Suppose we only need the newest active critical incident.
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.addQuery(10 'priority',11 '1',12)13
14incidentGR.orderByDesc(15 'sys_created_on',16)17
18incidentGR.setLimit(1)19
20incidentGR.query()21
22if (incidentGR.next()) {23 gs.info(24 'Newest critical incident: ' +25 incidentGR.getValue(26 'number',27 )28 )29}Notice that we limit the results because the requirement only needs one record.
Example: Query by Reference
Suppose we need active incidents assigned to a particular group.
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.addQuery(10 'assignment_group',11 groupSysId,12)13
14incidentGR.query()15
16while (incidentGR.next()) {17 gs.info(18 incidentGR.getValue(19 'number',20 )21 )22}For reference comparisons, query using the underlying reference value, usually the sys_id.
Example: Read Reference Display Information
If we need the assignment group's name for presentation:
1var groupName =2 incidentGR3 .assignment_group4 .getDisplayValue()5
6gs.info(7 'Assigned to: ' +8 groupName9)If we need the underlying ID:
1var groupSysId =2 incidentGR.getValue(3 'assignment_group',4 )Know which representation your logic needs.
Example: Update a Related Record
Suppose an Incident has a parent record that must be updated after a specific business event.
1var parentId =2 current.getValue(3 'parent',4 )5
6if (parentId) {7 var parentGR =8 new GlideRecord('task')9
10 if (11 parentGR.get(12 parentId13 )14 ) {15 parentGR.setValue(16 'work_notes',17 'Child incident was updated.'18 )19
20 parentGR.update()21 }22}This is a legitimate reason to use another GlideRecord inside a Business Rule: we are working with a separate record.
Still, think about the effects of the related update and what automation it may trigger.
Example: Count Records Efficiently
If the requirement is:
How many active critical incidents exist?
Use an aggregate query.
1var incidentGA =2 new GlideAggregate(3 'incident',4 )5
6incidentGA.addQuery(7 'active',8 true,9)10
11incidentGA.addQuery(12 'priority',13 '1',14)15
16incidentGA.addAggregate(17 'COUNT',18)19
20incidentGA.query()21
22if (incidentGA.next()) {23 gs.info(24 incidentGA.getAggregate(25 'COUNT',26 )27 )28}We do not need to load every Incident just to increment a JavaScript counter.
A Practical GlideRecord Checklist
Before writing a query, ask:
Which table do I actually need?
Start with the most appropriate table.
Which records do I actually need?
Add conditions before calling query().
Do I need all matching records?
If not, use setLimit().
Do I know the exact sys_id?
Consider get().
Do I only need a count or aggregate?
Consider GlideAggregate.
Am I querying inside another loop?
Look for ways to reduce repeated database access.
Am I retrieving data I already have?
Use the existing record instead.
Do I need the stored value or display value?
Choose getValue() or a display-value method intentionally.
Is this a destructive update or delete?
Test the query before modifying anything.
Does this query need to respect user access?
Use the appropriate security-aware data-access pattern.
Common GlideRecord Mistakes
Querying the entire table unnecessarily
Always filter when the requirement allows it.
Filtering in JavaScript instead of the database
Move conditions into the query.
Querying inside large loops
This can multiply database operations dramatically.
Using getRowCount() when only a count is required
Use GlideAggregate.
Forgetting query()
Adding conditions does not execute the query.
Forgetting next()
The GlideRecord must be positioned on a returned record before reading result fields.
Using display values when logic needs raw values
Understand the difference.
Using raw values when humans need display values
A sys_id is not useful in a user-facing message.
Updating the current record again inside a Before Business Rule
Modify current instead when appropriate.
Running bulk updates without testing the filter
Always verify the affected records first.
A Useful Mental Model
Think of GlideRecord as a database conversation.
First:
Choose the table
new GlideRecord(...)
Then:
Describe what you want
addQuery(...)
Then:
Ask ServiceNow for the results
query()
Then:
Move through those results
next()
Then:
Read or change data
getValue()setValue()
Finally, if required:
Persist the change
update()insert()deleteRecord()
Keeping those stages clear makes GlideRecord much easier to reason about.
Write Queries for the Database, Not the Loop
A strong GlideRecord script makes the database return the smallest useful result set.
A weak script often retrieves a large set and tries to work out what it really wanted afterward.
Prefer:
1incidentGR.addQuery(2 'active',3 true,4)5
6incidentGR.addQuery(7 'priority',8 '1',9)over querying every incident and checking those conditions manually inside the loop.
This single habit can make scripts both clearer and more efficient.
GlideRecord Is Powerful Because It Is Everywhere
Once you understand GlideRecord, the same fundamental patterns appear throughout ServiceNow server-side development.
You will see it in:
- Business Rules
- Script Includes
- Scheduled Script Executions
- Fix Scripts
- Background Scripts
- Scripted REST APIs
- server-side actions
- integration logic
That makes GlideRecord one of the most valuable APIs for a ServiceNow developer to understand properly.
But Don't Use GlideRecord Just Because You Can
ServiceNow provides specialized APIs for different jobs.
For example:
- use
GlideAggregatefor aggregates - use security-aware APIs where access enforcement is required
- use platform APIs when a purpose-built API already exists
- use declarative features where scripting is unnecessary
Good ServiceNow development is not about putting GlideRecord everywhere.
It is about choosing the simplest appropriate tool for the requirement.
Conclusion
GlideRecord is the foundation of a huge amount of server-side ServiceNow development.
The basic pattern is straightforward:
1var incidentGR =2 new GlideRecord('incident')3
4incidentGR.addQuery(5 'active',6 true,7)8
9incidentGR.query()10
11while (incidentGR.next()) {12 // process matching records13}But writing good GlideRecord code requires more than remembering the syntax.
You should also think about:
- query scope
- table size
- filtering
- result limits
- reference fields
- database versus display values
- repeated database access
- updates and triggered automation
- aggregates
- security
- maintainability
The most useful habits are simple:
- filter before querying
- retrieve only what you need
- avoid unnecessary queries inside loops
- use
get()when retrieving one known record - use
setLimit()when the result can be bounded - use
GlideAggregatefor aggregate requirements - test destructive queries before changing data
- keep reusable database logic organized
- understand the security context of the operation
Once these patterns become natural, GlideRecord stops feeling like just another ServiceNow API and becomes a predictable tool for building reliable server-side applications.

Learn the difference between ServiceNow Client Scripts and Business Rules, when to use each, common mistakes, performance considerations, and practical examples.