Home ServiceNow About
Developer guide • ServiceNow

ServiceNow GlideRecord CRUD Operations: A Complete Developer Guide

GlideRecord is how server-side scripts read and change data in ServiceNow. This guide covers creating, reading, updating and deleting records, then encoded queries, OR conditions, performance, access control, and the mistakes that cause most GlideRecord bugs.

Operations
4
Create, read, update, delete
Runs on
Server
Client scripts call it through GlideAjax
Access controls
Not checked
Use GlideRecordSecure to enforce ACLs
Bulk updates
Rules run
updateMultiple() fires Business Rules

01 What is GlideRecord?

Every table in ServiceNow, from incident to a custom u_ table, can be reached through GlideRecord. The official ServiceNow documentation describes it as the class that handles database operations for server scripts. It hides the database underneath, so you work with records and fields instead of SQL.

If you know SQL, this query:

-- SQL (not used in ServiceNow)
SELECT * FROM incident WHERE priority = 1 AND state = 2

is written like this with GlideRecord:

var grIncident = new GlideRecord('incident');
grIncident.addQuery('priority', 1);
grIncident.addQuery('state', 2);
grIncident.query();
while (grIncident.next()) {
    gs.info('Incident: ' + grIncident.getValue('number'));
}
Every read follows the same four steps
new GlideRecord()Pick the table.
addQuery()Add conditions. Nothing runs yet.
query()Send the query to the database.
next()Move to each matching record in turn.

Where you use GlideRecord

GlideRecord belongs in server-side scripts. The ServiceNow Community developer articles list the usual places:

  • Business Rules: run logic when a record is inserted, updated, deleted or queried.
  • Script Includes: reusable server-side functions and classes.
  • UI Actions (server side): work done when a user clicks a form button or link.
  • Scheduled jobs and fix scripts: batch processing and one-off data changes.
  • Flow Designer script steps and workflow scripts: custom logic inside automation.
What about client scripts? A client-side GlideRecord exists, but ServiceNow discourages it, and it is not available in Service Portal client code or scoped client scripts. Without a callback it makes a synchronous call that freezes the form while it waits. Use GlideAjax to call a Script Include instead (example in section 09).

02 Where the Glide name comes from

Fred Luddy started the company as Glidesoft in 2003. According to Wikipedia's ServiceNow history, it was incorporated in California in 2004 and renamed Service-now.com in 2006. The company name changed, but "Glide" stayed as the name of the platform's scripting APIs. The ServiceNow Glide APIs guide gives an overview of the family.

The Glide APIs you will meet first

APIRuns onUsed for
GlideRecordServerCreating, reading, updating and deleting records
GlideRecordSecureServerThe same, with access controls enforced
GlideAggregateServerCOUNT, SUM, AVG, MIN and MAX in the database
GlideSystem (gs)ServerLogging, the current user, dates, properties
GlideQueryServerA stricter, chainable query API built on GlideRecord
GlideAjaxClientAsynchronous calls from the browser to a Script Include
GlideForm (g_form)ClientReading and changing fields on the open form
GlideUser (g_user)ClientThe signed-in user's name, roles and ID
Which JavaScript version? Global server scripts still run as ES5, so arrow functions, let/const and template literals fail there. Scoped applications can switch on ECMAScript 2021 mode (available since the Tokyo release) in the application's settings. The examples in this guide use ES5 so they work in both places.

03 CRUD operations

CRUD stands for create, read, update and delete. The ServiceNow real-time scenario guide walks through all four. Here is the short version, method by method.

OperationOne recordMany recordsReturns
Createinitialize() then insert()Call insert() in a loopThe new sys_id, or null if the insert failed
Readget()query() then next()get() and next() return true or false
Updateupdate()updateMultiple()update() returns the sys_id, or null
DeletedeleteRecord()deleteMultiple()deleteRecord() returns true or false

Create

Make a GlideRecord for the table, call initialize(), set the fields and call insert():

var grIncident = new GlideRecord('incident');
grIncident.initialize();
grIncident.setValue('short_description', 'Network connectivity issue');
grIncident.setValue('description', 'Users cannot reach internal applications');
grIncident.setValue('priority', 2);
grIncident.setValue('category', 'network');   // choice value, not the label
grIncident.setValue('caller_id', gs.getUserID());

var newSysId = grIncident.insert();
if (newSysId) {
    gs.info('Created incident ' + grIncident.getValue('number'));
} else {
    gs.error('Insert failed: ' + grIncident.getLastErrorMessage());
}

Choice fields store a value, which is often different from the label you see on the form. For the incident Category field the value is network and the label is "Network". Check the value in the field's choice list before you script it.

Read

Use get() for one record and query() with next() for several:

// One record by sys_id
var grIncident = new GlideRecord('incident');
if (grIncident.get('46d44a5dc0a8010e0006b48d9b0e4e6d')) {
    gs.info('Found: ' + grIncident.getValue('short_description'));
}

// One record by another field
var grByNumber = new GlideRecord('incident');
if (grByNumber.get('number', 'INC0001234')) {
    gs.info('Priority: ' + grByNumber.getValue('priority'));
}

// Several records
var grNew = new GlideRecord('incident');
grNew.addQuery('state', 1);              // New
grNew.addQuery('priority', '<=', 2);    // Priority 1 or 2
grNew.orderByDesc('sys_created_on');
grNew.query();
while (grNew.next()) {
    gs.info(grNew.getValue('number') + ': ' + grNew.getValue('short_description'));
}

Update

Read the record, change the fields, then call update(). Reference fields such as Assigned to hold a sys_id, not a user name:

var grIncident = new GlideRecord('incident');
if (grIncident.get('number', 'INC0001234')) {
    var grUser = new GlideRecord('sys_user');
    if (grUser.get('user_name', 'john.doe')) {
        grIncident.setValue('assigned_to', grUser.getUniqueValue());   // a sys_id
    }
    grIncident.setValue('state', 2);   // In Progress
    grIncident.work_notes = 'Assigned to the network team';   // journal field
    grIncident.update();
}

// Several records, one at a time
var grUnassigned = new GlideRecord('incident');
grUnassigned.addQuery('state', 1);
grUnassigned.addNullQuery('assignment_group');   // assignment group is empty
grUnassigned.query();
while (grUnassigned.next()) {
    grUnassigned.setValue('assignment_group', 'DEFAULT_GROUP_SYS_ID');
    grUnassigned.update();
}

To test for an empty field, use addNullQuery(), or addNotNullQuery() for the opposite. addQuery('assignment_group', 'empty') looks for the literal text "empty" and finds nothing.

Update many records with updateMultiple()

updateMultiple() applies the same change to every record the query matches, without a loop:

var grResolved = new GlideRecord('incident');
grResolved.addQuery('state', 6);                                     // Resolved
grResolved.addQuery('resolved_at', '<', gs.daysAgoStart(30));       // 30+ days ago
grResolved.setValue('state', 7);                                     // Closed
grResolved.updateMultiple();
updateMultiple() runs Business Rules updateMultiple() runs the table's Business Rules for the records it changes. To skip them, call setWorkflow(false) first, and only when that is what you intend. Two more rules: set values with setValue(), because direct assignment such as gr.state = 7 can update every record in the table instead of only the ones matched, and do not combine it with setLimit() or chooseWindow().

Delete

Use deleteRecord() for one record and deleteMultiple() for every record a query matches:

// One record
var grTest = new GlideRecord('u_test_table');
if (grTest.get('RECORD_SYS_ID')) {
    grTest.deleteRecord();
}

// Many records: check how many will go before you delete them
var grOld = new GlideRecord('u_temp_data');
grOld.addQuery('sys_created_on', '<', gs.daysAgoStart(90));   // older than 90 days
grOld.query();
gs.info('Records to delete: ' + grOld.getRowCount());   // fine for a one-off check
// grOld.deleteMultiple();   // uncomment once the count looks right
setLimit() does not protect a deleteMultiple() deleteMultiple() works on the whole query, and developers report that it ignores setLimit() and chooseWindow(). Test the conditions with a read first, as above, and run the delete in a sub-production instance. Do not count on being able to restore bulk-deleted records.

04 Building queries

Most of your time with GlideRecord goes into building queries. A query is built from conditions, and conditions are joined with AND unless you add an OR.

addQuery()

addQuery() takes a field and a value, or a field, an operator and a value:

gr.addQuery('priority', 1);                                // priority = 1
gr.addQuery('priority', '<=', 2);                         // priority <= 2
gr.addQuery('short_description', 'CONTAINS', 'network');   // LIKE '%network%'
gr.addQuery('opened_at', '>=', gs.daysAgoStart(7));        // opened in the last 7 days
gr.addNullQuery('assigned_to');                            // assigned_to is empty
gr.addNotNullQuery('resolved_at');                         // resolved_at has a value

Operators

OperatorMeaningExample
=Equals (the default)addQuery('state', '=', 1)
!=Not equaladdQuery('state', '!=', 7)
> >=Greater than, greater than or equaladdQuery('priority', '>=', 2)
< <=Less than, less than or equaladdQuery('priority', '<=', 2)
CONTAINSContains the textaddQuery('short_description', 'CONTAINS', 'error')
STARTSWITHStarts withaddQuery('number', 'STARTSWITH', 'INC')
ENDSWITHEnds withaddQuery('email', 'ENDSWITH', '@company.com')
INIn a listaddQuery('state', 'IN', '1,2,3')
NOT INNot in a listaddQuery('state', 'NOT IN', '6,7,8')

Dot-walking

You can follow a reference field to a field on the referenced record, which the community's GlideRecord hints and tips recommend over nested queries:

Two queries
var grUser = new GlideRecord('sys_user');
if (grUser.get('name', 'John Doe')) {
    var grIncident = new GlideRecord('incident');
    grIncident.addQuery('caller_id', grUser.getUniqueValue());
    grIncident.query();
}
One query with dot-walking
var grIncident = new GlideRecord('incident');
grIncident.addQuery('caller_id.name', 'John Doe');
grIncident.query();
while (grIncident.next()) {
    gs.info(grIncident.getValue('number'));
}

Sorting

var grIncident = new GlideRecord('incident');
grIncident.orderBy('priority');           // first sort, ascending
grIncident.orderByDesc('opened_at');      // then newest first
grIncident.query();

05 Encoded queries

An encoded query is a whole set of conditions written as one string, such as active=true^priority=1. It is the same format a list filter uses in the URL, which makes it easy to build a filter in the list view and paste it into a script. The ServiceNow developer training covers the format.

Syntax

PartMeaningExample
^ANDpriority=1^state=2
^OROR with the previous conditionpriority=1^ORpriority=2
^NQStart a new query; the two groups are joined with ORpriority=1^NQstate=2
ORDERBYSort ascending^ORDERBYopened_at
ORDERBYDESCSort descending^ORDERBYDESCpriority

addEncodedQuery()

var grIncident = new GlideRecord('incident');
grIncident.addEncodedQuery('active=true^priorityIN1,2^ORDERBYDESCopened_at');
grIncident.query();
while (grIncident.next()) {
    gs.info(grIncident.getValue('number'));
}

Copy an encoded query from a list

The ServiceNow Scripting 101 guide describes the quickest way to get one:

1
Open the list

Go to the table's list view, for example incident.list.

2
Build the filter

Add every condition in the condition builder.

3
Run it

Click Run and check that the list shows the records you expect.

4
Copy the query

Right-click the last part of the breadcrumb and choose Copy query.

Dynamic values with javascript:

The encoded query documentation allows a javascript: prefix for values worked out when the query runs:

// Opened in the last 7 days
grIncident.addEncodedQuery('opened_at>=javascript:gs.daysAgoStart(7)');

// Assigned to the current user
grIncident.addEncodedQuery('assigned_to=javascript:gs.getUserID()');

// Opened this month
grIncident.addEncodedQuery('opened_at>=javascript:gs.beginningOfThisMonth()');
Write down what the query does Encoded queries are hard to read a few months later. Put a one-line comment above each one, or build it from labelled pieces as shown in section 09.

06 OR conditions

OR is where most GlideRecord queries go wrong, because the way conditions group together is not obvious from the code.

addOrCondition()

addQuery() returns a condition object. Call addOrCondition() on that object, not on the GlideRecord:

// priority = 1 OR priority = 2
var grIncident = new GlideRecord('incident');
var condition = grIncident.addQuery('priority', 1);
condition.addOrCondition('priority', 2);
grIncident.query();

AND with OR

// (priority = 1 OR priority = 2) AND state = 1 AND active = true
var grIncident = new GlideRecord('incident');
var condition = grIncident.addQuery('priority', 1);
condition.addOrCondition('priority', 2);
grIncident.addQuery('state', 1);
grIncident.addQuery('active', true);
grIncident.query();

^NQ for two separate groups

When you need two whole groups of conditions joined with OR, use ^NQ in an encoded query:

// (active AND first name Ben or Simon) OR (inactive AND first name Emma or Brenda)
var grUser = new GlideRecord('sys_user');
grUser.addEncodedQuery('active=true^first_name=Ben^ORfirst_name=Simon' +
    '^NQactive=false^first_name=Emma^ORfirst_name=Brenda');
grUser.query();

The community's query performance practices note that ^NQ queries can be slow on large tables. Two simpler queries are sometimes faster.

IN instead of many ORs on one field

var grIncident = new GlideRecord('incident');
grIncident.addQuery('state', 'IN', '1,2,3');
grIncident.query();

// The same as an encoded query
grIncident.addEncodedQuery('stateIN1,2,3');

07 Performance

A careless GlideRecord query can slow down the whole instance. The ServiceNow Support knowledge base and the community guides agree on the main points.

setLimit()

var grIncident = new GlideRecord('incident');
grIncident.addQuery('active', true);
grIncident.setLimit(10);     // at most 10 records
grIncident.query();

If you only need to know whether any record matches, setLimit(1) lets the database stop at the first match.

Filter on indexed fields

sys_id is always indexed. On task tables, number, active, sys_created_on and sys_updated_on usually are too, but check the table's indexes on your instance. A condition on an unindexed custom field can mean a full table scan on a large table; add an indexed condition alongside it, or ask for an index.

Count with GlideAggregate

Slow
var count = 0;
var grIncident = new GlideRecord('incident');
grIncident.addQuery('priority', 1);
grIncident.query();
while (grIncident.next()) {
    count++;
}
Fast
var gaIncident = new GlideAggregate('incident');
gaIncident.addQuery('priority', 1);
gaIncident.addAggregate('COUNT');
gaIncident.query();
var total = gaIncident.next() ? gaIncident.getAggregate('COUNT') : 0;
gs.info('Count: ' + total);

getRowCount() looks like a shortcut, but it still runs the full query, so use GlideAggregate for counts in production code too.

Paging with chooseWindow()

The pagination guide uses chooseWindow(first, last), where first is included, last is not, and rows count from 0:

// Page 2 at 50 records a page: rows 50 to 99
var grIncident = new GlideRecord('incident');
grIncident.addQuery('active', true);
grIncident.orderByDesc('opened_at');
grIncident.chooseWindow(50, 100);
grIncident.query();

Large offsets such as chooseWindow(10000, 10050) get slow. For deep paging, filter on a sorted field instead, for example "opened before the last record of the previous page".

Common performance mistakes

MistakeEffectFix
Query without conditionsReads the whole tableAlways add conditions, and setLimit() where you can
update() in a loop over many recordsOne database write and one set of Business Rules per recordupdateMultiple() with setValue(); add setWorkflow(false) only if skipping the rules is intended
Counting with a loop or getRowCount()Reads every matching recordGlideAggregate with COUNT
CONTAINS on large text fieldsFull table scanNarrow with indexed conditions first
Misspelt field nameThe condition is dropped and the query can return every recordCheck field names; see section 08

08 Common mistakes

These come up again and again in the community's GlideRecord hints and tips.

1. Storing gr.field instead of getValue()

Bug
var sysIds = [];
var grIncident = new GlideRecord('incident');
grIncident.addQuery('priority', 1);
grIncident.query();
while (grIncident.next()) {
    sysIds.push(grIncident.sys_id);   // a GlideElement, not a string
}
// Every entry now shows the last record's sys_id
Fix
while (grIncident.next()) {
    sysIds.push(grIncident.getValue('sys_id'));   // or grIncident.getUniqueValue()
}

grIncident.sys_id is an object that belongs to the GlideRecord, and its value changes each time next() runs. getValue() returns a plain string you can keep.

2. Forgetting query()

var grIncident = new GlideRecord('incident');
grIncident.addQuery('priority', 1);
// grIncident.query();   // missing: next() returns false and the loop never runs
while (grIncident.next()) {
    gs.info(grIncident.getValue('number'));
}

3. Not checking what get() returned

As the developer forum explains, get() returns false when no record matches, and your code carries on unless you check:

Bug
var grIncident = new GlideRecord('incident');
grIncident.get('SYS_ID_THAT_DOES_NOT_EXIST');   // returns false
grIncident.setValue('short_description', 'Updated');
grIncident.update();   // no record is loaded, so this inserts a new incident
Fix
var grIncident = new GlideRecord('incident');
if (grIncident.get(sysId)) {
    grIncident.setValue('short_description', 'Updated');
    grIncident.update();
} else {
    gs.warn('Incident not found: ' + sysId);
}

The new record gets a new sys_id, not the one you asked for, so the mistake is easy to miss until duplicates appear.

4. A misspelt field name

var grIncident = new GlideRecord('incident');
grIncident.addQuery('prioritty', 1);   // typo
grIncident.query();                    // no error, and the condition is dropped

By default an invalid condition is ignored, so the query can return every record in the table, which is dangerous before an update or delete. Check names with isValidField() when they come from input. The system property glide.invalid_query.returns_no_rows makes invalid queries return nothing instead; check whether it is set on your instance. GlideQuery fails with an error instead, which is one reason to use it in new code.

if (grIncident.isValidField(fieldName)) {
    grIncident.addQuery(fieldName, value);
}

5. Cross-scope access

A scoped application can only reach another scope's table if that table allows it. Many global tables, sys_user among them, can be read from any scope by default. For a table that does not allow it, the call is blocked and a cross-scope access message appears in the system log. Check the table's Application Access settings, or ask for a cross-scope privilege record.

Debugging helpers

MethodUse it to
gr.getEncodedQuery()See the query GlideRecord actually built
gr.getRowCount()Check how many records matched (debugging only)
gr.getTableName()Confirm which table you are on
gr.getLastErrorMessage()Read why an insert or update failed
gs.info()Write to System Logs > All

09 Good habits

These habits, collected from the practical quick guide and community experience, keep GlideRecord code readable and safe.

Name variables after the table

// Hard to follow
var gr = new GlideRecord('incident');
var gr2 = new GlideRecord('sys_user');

// Clear
var grIncident = new GlideRecord('incident');
var grUser = new GlideRecord('sys_user');

A plain gr also collides easily with another script's gr in the same scope, which causes some very confusing bugs in Business Rules.

Use getValue() and getDisplayValue()

var incidentData = {
    number: grIncident.getValue('number'),
    callerSysId: grIncident.getValue('caller_id'),
    callerName: grIncident.getDisplayValue('caller_id'),   // the user's name
    group: grIncident.getDisplayValue('assignment_group')
};

Handle the failure path

function updateIncidentState(sysId, newState) {
    var grIncident = new GlideRecord('incident');
    if (!grIncident.get(sysId)) {
        return { success: false, error: 'Record not found' };
    }
    grIncident.setValue('state', newState);
    if (grIncident.update()) {
        return { success: true, number: grIncident.getValue('number') };
    }
    return { success: false, error: grIncident.getLastErrorMessage() };
}

Build long encoded queries from labelled pieces

// Active P1/P2 incidents opened in the last 7 days for the Network group,
// not resolved or closed, by priority then newest first
var QUERY = 'active=true' +
    '^priorityIN1,2' +
    '^opened_at>=javascript:gs.daysAgoStart(7)' +
    '^assignment_group.name=Network' +
    '^stateNOT IN6,7,8' +
    '^ORDERBYpriority' +
    '^ORDERBYDESCopened_at';

var grIncident = new GlideRecord('incident');
grIncident.addEncodedQuery(QUERY);
grIncident.query();

Call the server from the client with GlideAjax

Client script
var ga = new GlideAjax('IncidentAjax');
ga.addParam('sysparm_name', 'getShortDescription');
ga.addParam('sysparm_incident_id', g_form.getUniqueValue());
ga.getXMLAnswer(function(answer) {
    g_form.addInfoMessage('Short description: ' + answer);
});
Script Include (client callable)
var IncidentAjax = Class.create();
IncidentAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    getShortDescription: function() {
        var grIncident = new GlideRecordSecure('incident');   // respects the caller's access
        if (grIncident.get(this.getParameter('sysparm_incident_id'))) {
            return grIncident.getValue('short_description');
        }
        return '';
    },
    type: 'IncidentAjax'
});
12 ServiceNow Tips and Tricks: Shortcuts, Table Commands and List Filters

10 Security and access control

GlideRecord does not check access controls (ACLs). Server-side code sees every record and field, whoever triggered it. That is fine for system jobs and a data leak waiting to happen in code that acts for a user. The ServiceNow Developer Blog and the ServiceNow Community both explain the difference.

GlideRecord and GlideRecordSecure

GlideRecordGlideRecordSecure
ACLsNot checked; call canRead(), canWrite() yourselfChecked on every read and write
Records the user cannot readReturnedSkipped
Fields the user cannot readReturnedCome back empty
Fields the user cannot writeWrittenNot written
SpeedSlightly fasterSlightly slower (ACL checks)
Use forSystem jobs, migrations, admin scriptsAnything that acts for a user: GlideAjax, REST, portal widgets
var grsIncident = new GlideRecordSecure('incident');
grsIncident.addQuery('active', true);
grsIncident.query();
while (grsIncident.next()) {
    // Only records this user can read; unreadable fields are empty
    gs.info(grsIncident.getValue('number'));
}

Checking ACLs yourself with GlideRecord

var grIncident = new GlideRecord('incident');
grIncident.addQuery('active', true);
grIncident.query();
while (grIncident.next()) {
    if (!grIncident.canRead()) {
        continue;
    }
    if (grIncident.caller_id.canRead()) {
        gs.info('Caller: ' + grIncident.getDisplayValue('caller_id'));
    }
}

Security methods

// Record level
gr.canRead();     gr.canWrite();     gr.canDelete();     gr.canCreate();

// Field level
gr.short_description.canRead();
gr.short_description.canWrite();

// Roles and groups
gs.hasRole('itil');
gs.getUser().isMemberOf('Network');   // group name or sys_id

Business Rules, system fields and ACLs are separate switches

CallWhat it changesWhat it does not change
setWorkflow(false)Business Rules and other engines (such as notifications) do not run for this record's next writeACLs
autoSysFields(false)sys_updated_on, sys_updated_by, sys_mod_count and the other system fields are not setBusiness Rules, ACLs
current.setAbortAction(true)Cancels the insert or update that triggered a before Business RuleACLs
GlideRecord instead of GlideRecordSecureNo ACL checks, in any script contextBusiness Rules

Security checklist

  • Default to GlideRecordSecure in code that runs for a user: GlideAjax, Scripted REST APIs, portal widgets.
  • Treat sys_ids and query text from the client as untrusted. addQuery(field, value) keeps a value as a value, but a string passed into addEncodedQuery() can add its own ^OR conditions. Never build an encoded query by joining user input.
  • Check canDelete() before deleting on a user's behalf.
  • Log with gs.info(), gs.warn() and gs.error(). gs.log() is not available in scoped applications.
  • Ask for the least access a scoped app needs: read rather than write, one table rather than all.
A simple rule If the code runs because a user did something (a UI Action, a client script through GlideAjax, a REST call), use GlideRecordSecure. If it is a background job with no user behind it, GlideRecord is fine.

More ServiceNow guides are in the ServiceNow category on this blog.

11 Frequently Asked Questions

GlideRecord is ServiceNow's server-side JavaScript API for working with table data without writing SQL. You use it in Business Rules, Script Includes, scheduled jobs and other server scripts to create, read, update and delete records.
Yes. updateMultiple() runs the table's Business Rules unless you call setWorkflow(false) first. Set field values with setValue() before calling it, not by direct assignment.
addQuery() adds one condition at a time: a field, an optional operator and a value. addEncodedQuery() takes a whole condition string such as priority=1^state=2, usually copied from a list filter. They run the same way; encoded queries are easier for long filters with OR groups.
gr.field_name is a GlideElement object that belongs to the GlideRecord, and its value changes every time next() moves to the next record. getValue('field_name') returns the value as a plain string, so it is safe to store in an array or object.
Filter on indexed fields, use setLimit() when you need only a few records, count with GlideAggregate instead of looping or getRowCount(), avoid large chooseWindow() offsets and check field names, because an invalid field can make a query return the whole table.
Client-side GlideRecord exists but ServiceNow discourages it, and it is not available in Service Portal widgets' client code or scoped client scripts. Use GlideAjax to call a Script Include, or g_form.getReference() with a callback for a single reference lookup.
GlideRecord does not check access controls. GlideRecordSecure checks ACLs on every read and write, so it only returns records and fields the current user may see. Use GlideRecordSecure when the script acts for a user.
It stops ServiceNow from setting sys_updated_on, sys_updated_by, sys_mod_count and the other system fields on the next insert or update. It is useful for data migrations that must keep the original audit values.
GlideQuery, added in the Paris release, wraps GlideRecord with a stricter, chainable API that fails loudly on invalid fields. It is a good choice for new code; GlideRecord is still everywhere in existing scripts, so you need to know both.

12 Abbreviations & Glossary

ACL
Access control list: a rule that decides who can read, write, create or delete a record or field.
API
Application programming interface.
Business Rule
Server-side script that runs when a record is queried, inserted, updated or deleted.
CRUD
Create, read, update, delete.
Dot-walking
Following a reference field to a field on the referenced record, such as caller_id.name.
Encoded query
A set of conditions written as one string, joined with ^.
ES5
ECMAScript 5, the JavaScript version global server scripts run.
GlideAggregate
Server API for COUNT, SUM and other aggregates computed in the database.
GlideAjax
Client API for asynchronous calls to a client-callable Script Include.
GlideElement
The object behind each field on a GlideRecord, such as gr.priority.
GlideRecordSecure
A GlideRecord that enforces ACLs.
gs
GlideSystem: server utilities for logging, users, dates and properties.
NQ
New query: joins two groups of conditions with OR in an encoded query.
Scope
The application namespace a script runs in, such as global or x_myapp.
Script Include
A reusable server-side script, often a class, that other scripts call.
sys_id
The 32-character unique ID of every record.
Link copied to clipboard!
@
Previous Post
Salesforce Business Rules Engine Guide
Next Post
ServiceNow Business Rules: Complete Developer Guide 2026
Archives by Year
2026 17 2025 16 2024 2 2023 9 2022 8 2021 4 2020 18 2019 16 2018 21 2017 34 2016 44 2015 54 2014 30 2013 31 2012 46 2011 114 2010 162
Search Blog

One response to “ServiceNow GlideRecord CRUD Guide | Developer Tutorial”

  1. Caution: The updateMultiple() method updates ALL matching records without triggering Business Rules. Use it carefully and only when you intentionally want to bypass Business Rules for bulk operations.

    this is not accurate, updateMultiple DOES run business rules

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Jitendra Zaa

Subscribe now to keep reading and get access to the full archive.

Continue Reading