Skip to content
IT Canvass
Development · Lesson

GlideRecord

Quick answer

GlideRecord is the server-side Java-backed API for reading and writing table data. It is the single most-used class in ServiceNow, and almost every business rule, script include, scheduled job and background script leans on it.

Key takeaways

  • The four operations
  • Building query conditions
  • getValue, getDisplayValue, and the field trap
  • Dot-walking through references
  • Writing: insert, update, and the return value

GlideRecord is the server-side Java-backed API for reading and writing table data. It is the single most-used class in ServiceNow, and almost every business rule, script include, scheduled job and background script leans on it. Understanding not just how to query but how GlideRecord behaves, lazy loading, journalled writes, dot-walking, and its performance envelope, separates a scripter who copies snippets from one who can be trusted with production data.

The four operations

Every GlideRecord script is some combination of query, read, write, delete. The lifecycle is always: instantiate against a table, narrow with query conditions, execute, then iterate.

var gr = new GlideRecord('incident'); // 1. target a table gr.addQuery('active', true); // 2. build conditions gr.addQuery('priority', '1'); gr.orderByDesc('sys_created_on'); gr.query(); // 3. run it (nothing hits the DB before this) while (gr.next()) { // 4. iterate the result set gs.info(gr.getValue('number')); }

Two things to internalise. First, nothing touches the database until query(), building conditions is cheap, executing is not. Second, GlideRecord is a cursor: next() advances one row at a time; the whole result set is not loaded into memory at once.

Building query conditions

The condition builder is richer than addQuery(field, value). The three-argument form takes an operator, and several helpers cover ORs, ranges and null checks.

gr.addQuery('priority', '<=', '2'); // operator form gr.addQuery('short_description', 'CONTAINS', 'email'); gr.addNullQuery('assigned_to'); // field is empty gr.addNotNullQuery('assigned_to'); // field is set // OR conditions, chain with addOrCondition on a query object var q = gr.addQuery('state', '1'); q.addOrCondition('state', '2'); // state = 1 OR state = 2 // The pragmatic shortcut: paste an encoded query from a list breadcrumb gr.addEncodedQuery('active=true^priorityIN1,2^assigned_toISEMPTY');
Encoded queries are your friend. Build the filter visually in a list, right-click the breadcrumb → Copy query, and paste it into addEncodedQuery(). It is faster and less error-prone than hand-assembling complex AND/OR logic, and it guarantees the operators are valid.

Common operators: =, !=, >, <, >=, <=, IN, NOT IN, STARTSWITH, ENDSWITH, CONTAINS, DOES NOT CONTAIN, LIKE.

getValue, getDisplayValue, and the field trap

This is the number-one source of GlideRecord bugs. A field accessed directly (gr.assigned_to) returns a GlideElement object, not a string, it only looks like a string when concatenated. On a reference field that surprises people badly:

gr.getValue('assigned_to'); // → sys_id "6816f79c..." gr.getDisplayValue('assigned_to'); // → "Beth Anglin" gr.assigned_to.toString(); // → sys_id (GlideElement coerced) gr.assigned_to.getDisplayValue(); // → "Beth Anglin"

Rule: compare and store on getValue() (the sys_id); show the user getDisplayValue(). Always call getValue() explicitly rather than relying on implicit coercion, it makes intent obvious and avoids the object-vs-string trap in conditionals.

Dot-walking through references

Because a reference field points at another record, you can walk across the relationship in one expression, no second query needed. GlideRecord resolves it for you:

// From an incident, reach the caller's department manager's email gr.getValue('caller_id.department.dept_head.email'); // Works in queries too gr.addQuery('caller_id.vip', true); // incidents raised by VIP callers
Dot-walking is convenient but each hop is a join. Walking three or four references across a large table can be slow, for hot code paths, consider storing the value you need directly, or query the target table.

Writing: insert, update, and the return value

// UPDATE the current record in a loop while (gr.next()) { gr.setValue('priority', '2'); gr.update(); } // INSERT a brand-new record, initialize() first var ni = new GlideRecord('incident'); ni.initialize(); ni.setValue('short_description', 'Printer offline'); ni.setValue('caller_id', gs.getUserID()); var sysId = ni.insert(); // returns the new sys_id, or null on failure

Prefer setValue()/getValue() over direct assignment in scripts you care about, they are unambiguous about strings vs objects. insert() returns the new record's sys_id (or null if a data policy or ACL blocked it), and update() returns the sys_id of the updated row.

Deleting, and why deleteMultiple exists

// One record if (gr.get('number', 'INC0010023')) gr.deleteRecord(); // Many records, do NOT loop deleteRecord() var old = new GlideRecord('syslog'); old.addEncodedQuery('sys_created_onRELATIVELE@dayofweek@ago@90'); old.deleteMultiple(); // single efficient operation

get() is the shortcut for fetching exactly one record, pass a sys_id, or a field/value pair. It returns a boolean so you can guard the block.

Counting and aggregating without pulling rows

When you only need a count or a sum, never query all rows and count in a loop, that drags every record into memory. Use GlideAggregate, which pushes the maths to the database:

var ga = new GlideAggregate('incident'); ga.addQuery('active', true); ga.addAggregate('COUNT', 'priority'); // count grouped by priority ga.groupBy('priority'); ga.query(); while (ga.next()) { gs.info(ga.getValue('priority') + ': ' + ga.getAggregate('COUNT', 'priority')); } // Aggregates: COUNT, SUM, AVG, MIN, MAX

Handling dates & times

Date fields are best manipulated with GlideDateTime rather than string arithmetic, so time zones and DST are handled correctly:

var due = new GlideDateTime(gr.getValue('opened_at')); due.addDaysUTC(3); gr.setValue('due_date', due.getValue());

Key methods at a glance

addQuery(f,[op,]v)
Add an AND condition (optional operator).
addEncodedQuery(q)
Apply a full encoded query string.
addOrCondition(f,v)
OR branch on a query object.
addNullQuery(f)
Field is empty.
query()
Execute against the database.
next()
Advance the cursor; false when done.
get(f,v)
Fetch one record; returns boolean.
getValue(f)
Raw value / sys_id (string).
getDisplayValue(f)
Human-readable display value.
setValue(f,v)
Set a field for the next write.
insert()
Insert; returns new sys_id or null.
update()
Persist changes to current row.
deleteRecord()
Delete the current row.
deleteMultiple()
Delete all matched rows efficiently.
setLimit(n)
Cap the number of rows returned.
getRowCount()
Row count (avoid on large sets).

Performance & safety

  • Filter at the database, not in script. Add every condition you can to the query; don't next() through everything and if inside the loop.
  • Use setLimit() when you only need the first N rows, it stops the DB fetching more.
  • Avoid getRowCount() on large tables, it can force a full scan; use GlideAggregate COUNT instead.
  • Mind the query business rule. A GlideRecord run as a normal user is filtered by ACLs; setWorkflow(false) and autoSysFields(false) control whether business rules and audit fields fire on writes.
  • GlideRecordSecure enforces ACLs in server scripts where you explicitly want row-level security applied.
The classic production incident: a background script with an unbounded query() and a update() in the loop, run against a million-row table with setWorkflow(true). Every update fires business rules, which fire more queries. Always test with setLimit(1) and gs.info() before you let a bulk write run for real.

Common mistakes

  • Forgetting query(), the loop silently never runs.
  • Using a field in string context and getting a sys_id where you expected a name.
  • Looping deleteRecord() or counting in script instead of deleteMultiple() / GlideAggregate.
  • Unbounded bulk updates that fire business rules a million times.
  • Assuming a server script sees all rows, as a non-admin user, ACLs filter the result.

Want to learn this properly?

Our live, instructor-led ServiceNow Training covers this hands-on, with real projects and a certification path.

Check your understanding

  1. Which method actually executes the query?

    • A. addQuery()
    • B. query()
    • C. next()
    Show answer

    B. query()

    addQuery only builds the filter; query() runs it.

  2. GlideRecord is primarily which kind of API?

    • A. Client-side
    • B. Server-side
    • C. CSS
    Show answer

    B. Server-side

    It is a server-side API.

  3. How do you safely read a field's value?

    • A. gr.field
    • B. gr.getValue('field')
    • C. gr.print()
    Show answer

    B. gr.getValue('field')

    getValue returns a string reliably.

Frequently asked questions

What does the term GlideRecord refer to in ServiceNow?

GlideRecord is the server-side Java-backed API for reading and writing table data. It is the single most-used class in ServiceNow, and almost every business rule, script include, scheduled job and background script leans on it.

What else is worth knowing about GlideRecord?

Every GlideRecord script is some combination of query, read, write, delete.

What is the practical takeaway on GlideRecord?

The lifecycle is always: instantiate against a table, narrow with query conditions, execute, then iterate.

What tends to go wrong with GlideRecord?

Using a field in string context and getting a sys_id where you expected a name. Unbounded bulk updates that fire business rules a million times. Assuming a server script sees all rows, as a non-admin user, ACLs filter the result.
CallWhatsAppEnquire