AR
AgentRuss
ServiceNow GlideRecord guide showing server-side database queries, filters, record iteration, updates, and performance best practices.
AgentRuss Guide

ServiceNow GlideRecord Explained: Queries, Examples & Best Practices

Written by
AgentRuss
Published

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:

JavaScript
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:

JavaScript
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:

JavaScript
1incidentGR.query()

After the query executes, we can move through the results using:

JavaScript
1incidentGR.next()

That gives us the standard GlideRecord pattern:

  1. create the GlideRecord
  2. add conditions
  3. execute the query
  4. iterate through the results
  5. read or modify records

The Basic GlideRecord Query

Let's find active incidents.

JavaScript
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:

JavaScript
1while (incidentGR.next()) {
2 // process record
3}

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:

JavaScript
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:

JavaScript
1var incidentGR =
2 new GlideRecord('incident')

or:

JavaScript
1var userGR =
2 new GlideRecord('sys_user')

or:

JavaScript
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:

JavaScript
1addQuery()

For example:

JavaScript
1incidentGR.addQuery(
2 'priority',
3 '1',
4)

This asks for records where Priority equals 1.

You can also provide an operator.

For example:

JavaScript
1incidentGR.addQuery(
2 'sys_created_on',
3 '>=',
4 '2026-01-01 00:00:00',
5)

The general pattern is:

JavaScript
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:

JavaScript
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:

  • =
  • !=
  • >
  • <
  • >=
  • <=
  • IN
  • NOT IN
  • LIKE
  • STARTSWITH
  • ENDSWITH
  • CONTAINS

For example:

JavaScript
1incidentGR.addQuery(
2 'number',
3 'STARTSWITH',
4 'INC',
5)

Or:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

  1. open the table list
  2. create the filter using ServiceNow's condition builder
  3. verify that the results are correct
  4. copy the encoded query
  5. 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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
1incidentGR.getValue(
2 'short_description',
3)

For example:

JavaScript
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:

JavaScript
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:

JavaScript
1var callerName =
2 incidentGR
3 .caller_id
4 .getDisplayValue()

Now we get the display value of the caller rather than the raw sys_id.


Why getValue() Is Useful

Consider:

JavaScript
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:

JavaScript
1gs.info(
2 'Caller: ' +
3 incidentGR
4 .caller_id
5 .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:

JavaScript
1incidentGR.caller_id.manager.email

This 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:

JavaScript
1var callerGR =
2 incidentGR
3 .caller_id
4 .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:

JavaScript
1orderBy()

For example:

JavaScript
1incidentGR.orderBy(
2 'number',
3)

For descending order:

JavaScript
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:

JavaScript
1setLimit()

For example:

JavaScript
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:

JavaScript
1while (incidentGR.next()) {
2 if (
3 incidentGR.getValue(
4 'priority',
5 ) === '1'
6 ) {
7 // process
8 }
9}

when you could simply add:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
1deleteRecord()

For example:

JavaScript
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:

JavaScript
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:

JavaScript
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 count
26 )
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:

JavaScript
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:

JavaScript
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 user
14}

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:

JavaScript
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:

JavaScript
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:

JavaScript
1var taskGR =
2 new GlideRecord('task')
3
4taskGR.query()

with:

JavaScript
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:

JavaScript
1current

If the value you need is already on current, do not create another GlideRecord query just to retrieve the same record.

For example, avoid:

JavaScript
1var incidentGR =
2 new GlideRecord('incident')
3
4incidentGR.get(
5 current.getUniqueValue()
6)

just to read:

JavaScript
1incidentGR.getValue(
2 'priority',
3)

when:

JavaScript
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:

JavaScript
1if (
2 recordGR.isValidRecord()
3) {
4 // safe to continue
5}

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:

JavaScript
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:

JavaScript
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 incidentGR
34 .getUniqueValue()
35 )
36 }
37
38 return incidents
39 },
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:

JavaScript
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 the
15 // security context allows
16}

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:

  1. determine whether the data is already available
  2. consider declarative platform features
  3. 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:

JavaScript
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.

JavaScript
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.

JavaScript
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:

JavaScript
1var groupName =
2 incidentGR
3 .assignment_group
4 .getDisplayValue()
5
6gs.info(
7 'Assigned to: ' +
8 groupName
9)

If we need the underlying ID:

JavaScript
1var groupSysId =
2 incidentGR.getValue(
3 'assignment_group',
4 )

Know which representation your logic needs.


Suppose an Incident has a parent record that must be updated after a specific business event.

JavaScript
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 parentId
13 )
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.

JavaScript
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:

JavaScript
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 GlideAggregate for 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:

JavaScript
1var incidentGR =
2 new GlideRecord('incident')
3
4incidentGR.addQuery(
5 'active',
6 true,
7)
8
9incidentGR.query()
10
11while (incidentGR.next()) {
12 // process matching records
13}

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 GlideAggregate for 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.