ServiceNow Development
Jitendra's Blog
COMPLETE DEVELOPER GUIDE

ServiceNow GlideRecord CRUD Operations

Master database operations with encoded queries, OR conditions, and performance optimization techniques

Video Tutorial
9
FAQs Answered
Security Best Practices
Performance Tips

1 What is GlideRecord?

In ServiceNow, GlideRecord is a fundamental and incredibly powerful JavaScript class that provides the primary way to interact with the ServiceNow database from server-side scripts. According to the official ServiceNow documentation, GlideRecord is a built-in class in ServiceNow's server-side scripting environment that abstracts away the complexity of direct database access.

Think of GlideRecord as ServiceNow's equivalent to SQL queries, but within a JavaScript context. Instead of writing:

// SQL equivalent (NOT used in ServiceNow)
SELECT * FROM incident WHERE priority = 1 AND state = 2

You write:

// GlideRecord equivalent
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.addQuery('state', 2);
gr.query();
while (gr.next()) {
    gs.info('Incident: ' + gr.number);
}

Where to Use GlideRecord

GlideRecord is predominantly used in server-side scripts. As explained in the ServiceNow Community developer articles, the primary use cases include:

Important: While client-side GlideRecord technically exists, it is deprecated and strongly discouraged. It causes synchronous network calls that freeze the browser. Always use GlideAjax instead for client-side database operations.

2 History of Glide APIs

The "Glide" name has an interesting origin story. In 2003, CEO Fred Luddy founded a company called GlideSoft, which specialized in IT Service Management applications. According to Wikipedia's ServiceNow history, the company was incorporated in California in 2004 and later rebranded to ServiceNow in 2006.

While the company name changed, the "Glide" name stuck for the API framework! As noted in the ServiceNow Glide APIs guide, this framework incorporates various APIs, libraries, and technologies that aid developers in building and customizing applications.

The Glide API Family

Today, the Glide API family includes several related but distinct APIs:

API Name Scope Primary Purpose
GlideRecord Server-side Database CRUD operations
GlideSystem (gs) Server-side System utilities, logging, user info
GlideAggregate Server-side Aggregate functions (COUNT, SUM, AVG)
GlideAjax Client-side Async calls to server Script Includes
GlideForm (g_form) Client-side Form field manipulation
GlideUser (g_user) Client-side Current user information
Pro Tip: ServiceNow uses JavaScript (ECMAScript 5) for scripting. Modern ES6+ features like arrow functions, let/const, and template literals are NOT supported in server-side scripts.

3 CRUD Operations

CRUD stands for Create, Read, Update, and Delete - the four fundamental database operations. As documented in ServiceNow's real-time scenario guides, GlideRecord provides methods for all four operations.

Create (Insert)

To create a new record, instantiate a GlideRecord object, set field values, and call insert():

// Create a new incident
var gr = new GlideRecord('incident');
gr.initialize();  // Initialize a new record
gr.short_description = 'Network connectivity issue';
gr.description = 'Users unable to access internal applications';
gr.priority = 2;
gr.category = 'Network';
gr.caller_id = gs.getUserID();  // Current user

var sys_id = gr.insert();  // Returns the sys_id of the new record

if (sys_id) {
    gs.info('Created incident: ' + gr.number);
} else {
    gs.error('Failed to create incident');
}
Note: The insert() method returns the sys_id of the newly created record, or null if the insert fails. Always check the return value for error handling.

Read (Query)

Reading data is the most common GlideRecord operation. You can retrieve single records or multiple records:

// Get a single record by sys_id
var gr = new GlideRecord('incident');
if (gr.get('a1b2c3d4e5f6g7h8i9j0')) {  // Pass sys_id directly
    gs.info('Found: ' + gr.short_description);
}

// Get a single record by field value
var gr2 = new GlideRecord('incident');
if (gr2.get('number', 'INC0001234')) {  // field, value
    gs.info('Priority: ' + gr2.priority);
}

// Query multiple records
var gr3 = new GlideRecord('incident');
gr3.addQuery('state', 1);  // New incidents
gr3.addQuery('priority', '<=', 2);  // Priority 1 or 2
gr3.query();

while (gr3.next()) {
    gs.info(gr3.number + ': ' + gr3.short_description);
}

Update

To update a record, first query for it, then modify fields and call update():

// Update a single record
var gr = new GlideRecord('incident');
if (gr.get('number', 'INC0001234')) {
    gr.state = 2;  // In Progress
    gr.assigned_to = 'john.doe';
    gr.work_notes = 'Assigned to network team';
    gr.update();
    gs.info('Updated: ' + gr.number);
}

// Update multiple records (use with caution!)
var gr2 = new GlideRecord('incident');
gr2.addQuery('state', 1);
gr2.addQuery('assignment_group', 'empty');
gr2.query();

while (gr2.next()) {
    gr2.assignment_group = 'default_group_sys_id';
    gr2.update();
}
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.
// updateMultiple - bypasses Business Rules!
var gr = new GlideRecord('incident');
gr.addQuery('state', 6);  // Resolved
gr.addQuery('resolved_at', '<', gs.daysAgoStart(30));  // 30+ days ago
gr.setValue('state', 7);  // Closed
gr.updateMultiple();  // Updates all matching records at once

Delete

Delete records with deleteRecord() for single records or deleteMultiple() for batch operations:

// Delete a single record
var gr = new GlideRecord('u_test_table');
if (gr.get('sys_id_here')) {
    gr.deleteRecord();
    gs.info('Record deleted');
}

// Delete multiple records (use with EXTREME caution!)
var gr2 = new GlideRecord('u_temp_data');
gr2.addQuery('created', '<', gs.daysAgoStart(90));  // Older than 90 days
gr2.deleteMultiple();  // Deletes ALL matching records!
Warning: deleteMultiple() permanently removes ALL matching records. Unlike updateMultiple(), deleted data cannot be recovered unless you have backups. Always test with setLimit(1) first in development environments.

4 Query Syntax Deep Dive

GlideRecord provides multiple methods for building queries. Understanding the nuances of each method is crucial for writing efficient and correct code.

addQuery() Method

The addQuery() method is the primary way to add conditions to a GlideRecord query. It supports various overloaded signatures:

// Simple equality (field equals value)
gr.addQuery('priority', 1);  // WHERE priority = 1

// With operator
gr.addQuery('priority', '<=', 2);  // WHERE priority <= 2
gr.addQuery('short_description', 'CONTAINS', 'network');  // LIKE '%network%'
gr.addQuery('caller_id', '!=', 'abc123');  // WHERE caller_id != 'abc123'

// NULL checks
gr.addNullQuery('assigned_to');  // WHERE assigned_to IS NULL
gr.addNotNullQuery('resolved_at');  // WHERE resolved_at IS NOT NULL

// Date queries
gr.addQuery('opened_at', '>', gs.daysAgo(7));  // Last 7 days
gr.addQuery('opened_at', 'ON', gs.now());  // Today

Supported Operators

Operator Description Example
= Equals addQuery('state', '=', 1)
!= Not equals addQuery('state', '!=', 7)
> Greater than addQuery('priority', '>', 3)
>= Greater than or equal addQuery('priority', '>=', 2)
< Less than addQuery('priority', '<', 3)
<= Less than or equal addQuery('priority', '<=', 2)
CONTAINS Contains substring addQuery('short_description', 'CONTAINS', 'error')
STARTSWITH Starts with addQuery('number', 'STARTSWITH', 'INC')
ENDSWITH Ends with addQuery('email', 'ENDSWITH', '@company.com')
IN In list addQuery('state', 'IN', '1,2,3')
NOT IN Not in list addQuery('state', 'NOT IN', '6,7,8')

Dot-Walking in Queries

According to the ServiceNow developer community best practices, you can "dot-walk" through reference fields in queries to avoid multiple lookups:

// Instead of this (inefficient - two queries)
var userGr = new GlideRecord('sys_user');
userGr.addQuery('name', 'John Doe');
userGr.query();
if (userGr.next()) {
    var incGr = new GlideRecord('incident');
    incGr.addQuery('caller_id', userGr.sys_id);
    incGr.query();
    // ...
}

// Do this (efficient - single query with dot-walk)
var gr = new GlideRecord('incident');
gr.addQuery('caller_id.name', 'John Doe');  // Dot-walk to user's name
gr.query();
while (gr.next()) {
    gs.info(gr.number);
}

Ordering Results

// Sort by single field
var gr = new GlideRecord('incident');
gr.orderBy('opened_at');  // Ascending (oldest first)
gr.query();

// Sort descending
var gr2 = new GlideRecord('incident');
gr2.orderByDesc('opened_at');  // Descending (newest first)
gr2.query();

// Multiple sort fields
var gr3 = new GlideRecord('incident');
gr3.orderBy('priority');  // Primary sort
gr3.orderByDesc('opened_at');  // Secondary sort
gr3.query();

5 Encoded Queries

Encoded queries are URL-encoded strings representing complex query conditions. They're especially useful when building queries with the ServiceNow filter builder UI and then using those queries in scripts. As explained in the ServiceNow developer training, encoded queries simplify complex query building.

Encoded Query Syntax

The basic syntax uses these delimiters:

Symbol Meaning Example
^ AND priority=1^state=2
^OR OR priority=1^ORpriority=2
^NQ New Query (OR group) priority=1^NQstate=2
ORDERBY Sort ascending ^ORDERBYopened_at
ORDERBYDESC Sort descending ^ORDERBYDESCpriority

Using addEncodedQuery()

// Simple encoded query
var gr = new GlideRecord('incident');
gr.addEncodedQuery('priority=1^state=2');
gr.query();
while (gr.next()) {
    gs.info(gr.number);
}

// Complex encoded query with OR conditions
var gr2 = new GlideRecord('incident');
gr2.addEncodedQuery('priority=1^ORpriority=2^caller_id=abc123^category=inquiry');
gr2.query();

// With ordering
var gr3 = new GlideRecord('incident');
gr3.addEncodedQuery('active=true^priority<=2^ORDERBYDESCopened_at');
gr3.query();

How to Generate Encoded Queries

As described in the ServiceNow Scripting 101 guide, the easiest way to generate encoded queries is through the ServiceNow UI:

1
Navigate to the Table

Go to the list view of the table you want to query (e.g., incident.list)

2
Build Your Filter

Use the condition builder to create your filter with all conditions

3
Run the Query

Click "Run" to validate the query returns expected records

4
Copy the Encoded Query

Right-click on the breadcrumb text and select "Copy query"

Best Practice: Always add a comment above your encoded query explaining what it does. Encoded queries are not self-documenting and can be difficult to read months later.
// Query: Active, P1 or P2 incidents, opened in last 7 days,
// assigned to Network team, sorted by priority then opened date
var gr = new GlideRecord('incident');
gr.addEncodedQuery('active=true^priorityIN1,2^opened_at>=javascript:gs.daysAgo(7)^assignment_group.name=Network^ORDERBYpriority^ORDERBYDESCopened_at');
gr.query();

Dynamic Queries with JavaScript

According to the ServiceNow encoded query documentation, you can use javascript: prefix for dynamic values:

// Dynamic date calculation
var gr = new GlideRecord('incident');
gr.addEncodedQuery('opened_at>=javascript:gs.daysAgo(7)');  // Last 7 days
gr.query();

// Current user
var gr2 = new GlideRecord('incident');
gr2.addEncodedQuery('assigned_to=javascript:gs.getUserID()');
gr2.query();

// Beginning of current month
var gr3 = new GlideRecord('incident');
gr3.addEncodedQuery('opened_at>=javascript:gs.beginningOfThisMonth()');

6 OR Conditions & Complex Queries

Building complex queries with OR conditions requires understanding how GlideRecord chains conditions. This is one of the most frequently misunderstood aspects of GlideRecord scripting.

Using addOrCondition()

The addOrCondition() method adds an OR clause to an existing query condition:

// Find incidents with priority 1 OR priority 2
var gr = new GlideRecord('incident');
var qc = gr.addQuery('priority', 1);
qc.addOrCondition('priority', 2);
gr.query();

// This generates: WHERE priority = 1 OR priority = 2

while (gr.next()) {
    gs.info(gr.number + ' - Priority: ' + gr.priority);
}
Important: The addOrCondition() method must be called on the query condition object returned by addQuery(), not on the GlideRecord itself. This is a common source of errors.

Combining AND and OR

When combining AND and OR conditions, the query logic follows this pattern:

// Goal: (priority = 1 OR priority = 2) AND state = 1 AND active = true

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

// Resulting SQL: WHERE (priority = 1 OR priority = 2) AND state = 1 AND active = true

NQ (New Query) for Complex Logic

For queries that require grouping multiple conditions, use the ^NQ syntax in encoded queries:

// Goal: Find all active users named 'Ben' or 'Simon',
// OR all inactive users named 'Emma' or 'Brenda'

var gr = new GlideRecord('sys_user');
gr.addEncodedQuery('active=true^first_name=Ben^ORfirst_name=Simon^NQactive=false^first_name=Emma^ORfirst_name=Brenda');
gr.query();

// Logic: (active=true AND (first_name=Ben OR first_name=Simon))
//        OR (active=false AND (first_name=Emma OR first_name=Brenda))
Performance Tip: According to ServiceNow performance best practices, NQ queries can be slow. Consider breaking them into two separate queries for better performance.

Using IN Operator for Multiple Values

For simpler OR conditions on the same field, use the IN operator instead:

// Instead of multiple ORs
var gr = new GlideRecord('incident');
var qc = gr.addQuery('state', 1);
qc.addOrCondition('state', 2);
qc.addOrCondition('state', 3);

// Use IN operator (cleaner)
var gr2 = new GlideRecord('incident');
gr2.addQuery('state', 'IN', '1,2,3');
gr2.query();

// Or with encoded query
var gr3 = new GlideRecord('incident');
gr3.addEncodedQuery('stateIN1,2,3');
gr3.query();

7 Performance Optimization

GlideRecord queries can significantly impact system performance if not optimized properly. According to the ServiceNow Support knowledge base, there are several key strategies for optimizing performance.

Use setLimit()

The setLimit() method restricts the number of records returned, dramatically improving performance when you only need a subset of records:

// Without setLimit - retrieves ALL matching records into memory
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.query();  // Could return millions of records!

// With setLimit - much faster, only retrieves 10 records
var gr2 = new GlideRecord('incident');
gr2.addQuery('active', true);
gr2.setLimit(10);
gr2.query();  // Returns maximum 10 records
When setLimit(1) Matters Most: If your query condition matches thousands of records but you only need one, setLimit(1) makes a dramatic performance difference. The database stops after finding the first match.

Query on Indexed Fields

As highlighted in the ServiceNow Performance Best Practices article, always try to filter on indexed fields:

// Common indexed fields (check your specific table)
// - sys_id (always indexed)
// - number (on task tables)
// - sys_created_on
// - sys_updated_on
// - active

// Good: Filtering on indexed field
var gr = new GlideRecord('incident');
gr.addQuery('number', 'INC0001234');  // number is indexed
gr.query();

// Bad: Filtering on non-indexed field without other constraints
var gr2 = new GlideRecord('incident');
gr2.addQuery('u_custom_field', 'some_value');  // Custom field may not be indexed
gr2.query();  // Could be slow on large tables

Use GlideAggregate for Counting

Never use a loop to count records when you can use GlideAggregate:

// BAD: Counting with loop (slow, memory-intensive)
var count = 0;
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.query();
while (gr.next()) {
    count++;
}
gs.info('Count: ' + count);

// GOOD: Counting with GlideAggregate (fast, efficient)
var ga = new GlideAggregate('incident');
ga.addQuery('priority', 1);
ga.addAggregate('COUNT');
ga.query();
if (ga.next()) {
    var count = ga.getAggregate('COUNT');
    gs.info('Count: ' + count);
}

chooseWindow() for Pagination

As documented in the ServiceNow pagination optimization guide, use chooseWindow() for paginated results:

// Get records 51-100 (for page 2 with 50 per page)
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.orderByDesc('opened_at');
gr.chooseWindow(50, 100);  // (start, end) - 0-indexed
gr.query();

while (gr.next()) {
    gs.info(gr.number);
}

// Note: For large offsets, performance degrades
// Avoid chooseWindow(10000, 10050) - will be slow

Avoid Common Performance Pitfalls

Pitfall Impact Solution
Query without conditions Returns entire table Always add WHERE conditions
update() in while loop Slow, triggers many Business Rules Use updateMultiple() when appropriate
Counting with loop Memory-intensive Use GlideAggregate
Large LIKE searches Full table scan Use indexed fields first
Invalid field in query Query returns ALL records Validate field names

8 Common Errors & Troubleshooting

Even experienced ServiceNow developers encounter GlideRecord errors. According to the ServiceNow community's hints and tips guide, these are the most common issues.

1. Pointer vs. Value (getValue() vs gr.field)

This is the #1 source of bugs in GlideRecord code:

// BUG: Using gr.sys_id in a loop
var sysIds = [];
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.query();
while (gr.next()) {
    sysIds.push(gr.sys_id);  // WRONG! Pointer, not value
}
// Result: Array contains the SAME sys_id repeated (last record's ID)

// FIX: Use getValue() to get a snapshot
var sysIds = [];
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.query();
while (gr.next()) {
    sysIds.push(gr.getValue('sys_id'));  // CORRECT! Value snapshot
}
// Result: Array contains unique sys_ids
Critical: Always use getValue('field_name') when storing values for later use. Using gr.field_name returns a pointer that changes as you iterate through records.

2. Forgetting to Call query()

// BUG: Missing query() call
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
// gr.query();  // MISSING!
while (gr.next()) {  // Never executes - no results
    gs.info(gr.number);
}

// FIX: Always call query() before next()
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.query();  // Execute the query
while (gr.next()) {
    gs.info(gr.number);
}

3. GlideRecord.get() with Non-Existent Records

As explained in the ServiceNow developer forum, get() with a non-existent sys_id creates a new object:

// BUG: No existence check before update
var gr = new GlideRecord('incident');
gr.get('non_existent_sys_id');  // Returns false, but creates new object
gr.short_description = 'Updated description';
gr.update();  // INSERTS a new record with that sys_id!

// FIX: Always check the return value
var gr = new GlideRecord('incident');
if (gr.get('possibly_non_existent_sys_id')) {  // Returns true if found
    gr.short_description = 'Updated description';
    gr.update();
} else {
    gs.warn('Record not found');
}

4. Invalid Field Names in Query

If a field name doesn't exist, the query condition is silently ignored:

// BUG: Typo in field name
var gr = new GlideRecord('incident');
gr.addQuery('prioritty', 1);  // Typo: 'prioritty' instead of 'priority'
gr.query();
// Query runs without error but returns ALL records (no WHERE clause)

// FIX: Validate table and field names
var gr = new GlideRecord('incident');
if (gr.isValidField('priority')) {
    gr.addQuery('priority', 1);
    gr.query();
} else {
    gs.error('Invalid field name');
}

5. Scope Permission Issues

In scoped applications, GlideRecord may fail silently due to permissions:

// Script Include in custom scope 'x_myapp'
// May not have access to global tables

var gr = new GlideRecord('sys_user');  // Global table
gr.addQuery('user_name', 'admin');
gr.query();
if (gr.next()) {
    gs.info('Found: ' + gr.name);  // May return empty results
}

// Solution: Check scope access settings in Application Registry
// Or use GlideRecordSecure for tables with cross-scope access enabled

Debugging Tips

Tool/Method Purpose Usage
gr.getEncodedQuery() View generated query gs.info(gr.getEncodedQuery());
gr.getRowCount() Check result count gs.info(gr.getRowCount());
gr.getTableName() Confirm table gs.info(gr.getTableName());
gs.info() Log output Check System Logs > All
gr.getLastErrorMessage() Get error details After failed insert/update

9 Best Practices

Following best practices ensures your GlideRecord code is maintainable, performant, and bug-free. These recommendations are compiled from ServiceNow's practical quick guide and community expertise.

Naming Conventions

// BAD: Generic variable names
var gr = new GlideRecord('incident');
var gr2 = new GlideRecord('sys_user');
var gr3 = new GlideRecord('cmdb_ci');

// GOOD: Descriptive variable names with table hints
var grIncident = new GlideRecord('incident');
var grUser = new GlideRecord('sys_user');
var grCI = new GlideRecord('cmdb_ci');

// ALTERNATIVE: Prefix with table abbreviation
var gr_INC = new GlideRecord('incident');
var gr_USER = new GlideRecord('sys_user');

Always Use getValue() for References

// When comparing or storing reference values
var incidentData = {
    number: gr.getValue('number'),
    caller: gr.getValue('caller_id'),
    assignedTo: gr.getValue('assigned_to'),
    sysId: gr.getValue('sys_id')
};

// When getting display values from references
var callerName = gr.getDisplayValue('caller_id');  // Returns user's name
var groupName = gr.getDisplayValue('assignment_group');

Error Handling Pattern

// Robust error handling pattern
function updateIncident(sysId, newState) {
    var gr = new GlideRecord('incident');

    if (!gr.get(sysId)) {
        gs.error('Incident not found: ' + sysId);
        return { success: false, error: 'Record not found' };
    }

    gr.state = newState;

    if (gr.update()) {
        gs.info('Updated incident: ' + gr.number);
        return { success: true, number: gr.getValue('number') };
    } else {
        var errorMsg = gr.getLastErrorMessage();
        gs.error('Failed to update: ' + errorMsg);
        return { success: false, error: errorMsg };
    }
}

Comment Your Encoded Queries

// Query: Active P1/P2 incidents, opened in last 7 days,
//        assigned to Network team, excluding resolved/closed,
//        sorted by priority (asc) then opened date (desc)
var QUERY = 'active=true' +
    '^priorityIN1,2' +
    '^opened_at>=javascript:gs.daysAgo(7)' +
    '^assignment_group.name=Network' +
    '^stateNOT IN6,7,8' +
    '^ORDERBYpriority' +
    '^ORDERBYDESCopened_at';

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

Use setLimit() During Development

// During development/testing, limit results to avoid long waits
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.setLimit(5);  // Remove or increase in production
gr.query();

while (gr.next()) {
    gs.info(gr.number + ': ' + gr.short_description);
}

Server-Side Only for GlideRecord

Never use GlideRecord on the client side! It causes synchronous network calls that freeze the browser. Always use GlideAjax to call server-side Script Includes for database operations.
// CLIENT SIDE - Use GlideAjax
var ga = new GlideAjax('MyScriptInclude');
ga.addParam('sysparm_name', 'getIncidentDetails');
ga.addParam('sysparm_incident_id', 'abc123');
ga.getXML(function(response) {
    var answer = response.responseXML.documentElement.getAttribute('answer');
    console.log('Result: ' + answer);
});

// SERVER SIDE (Script Include) - Use GlideRecord
var MyScriptInclude = Class.create();
MyScriptInclude.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    getIncidentDetails: function() {
        var incId = this.getParameter('sysparm_incident_id');
        var gr = new GlideRecord('incident');
        if (gr.get(incId)) {
            return gr.getValue('short_description');
        }
        return '';
    },
    type: 'MyScriptInclude'
});

Video Tutorial

For a hands-on video walkthrough of GlideRecord basics, check out this excellent tutorial by Hardit Singh - ServiceNow Rising Star 2022-23, Manager at EY, and active ServiceNow Community contributor:

10 Security & Access Control

Understanding how GlideRecord handles security is critical for building secure ServiceNow applications. According to the ServiceNow Developer Blog, there are important differences between GlideRecord and GlideRecordSecure that every developer must understand.

GlideRecord vs GlideRecordSecure

As explained in the ServiceNow Community, the key difference is ACL enforcement:

Feature GlideRecord GlideRecordSecure
ACL Enforcement Manual - must call canRead(), canWrite() Automatic - enforces ACLs on every operation
Performance Faster (no ACL checks) Slightly slower (ACL overhead)
Use Case System scripts, admin operations User-facing features, sensitive data
Non-readable fields Returns actual values Returns NULL for restricted fields
Non-writable fields Writes succeed Sets to NULL, blocks write

Using GlideRecordSecure

GlideRecordSecure is the recommended approach when building features that interact with user data:

// GlideRecordSecure - Automatically enforces ACLs
var grs = new GlideRecordSecure('incident');
grs.addQuery('active', true);
grs.query();

while (grs.next()) {
    // Only returns records the current user can read
    // Fields user can't access return NULL
    gs.info(grs.number + ': ' + grs.short_description);
}

// No need for manual canRead() checks!

Manual ACL Checking with GlideRecord

If you must use GlideRecord (for performance or system-level operations), manually check ACLs:

// GlideRecord with manual ACL checks
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.query();

while (gr.next()) {
    // CRITICAL: Check if user can read this record
    if (!gr.canRead()) {
        continue;  // Skip records user can't access
    }

    // Check field-level access before displaying
    if (gr.canRead('caller_id')) {
        gs.info('Caller: ' + gr.getDisplayValue('caller_id'));
    }

    gs.info(gr.number);
}
Security Warning: Using GlideRecord without ACL checks can expose sensitive data! If a user doesn't satisfy an ACL, GlideRecordSecure blocks access, but GlideRecord returns the data anyway. Always use GlideRecordSecure for user-facing features.

Common Security Methods

// Check record-level permissions
gr.canRead()      // Can current user read this record?
gr.canWrite()     // Can current user update this record?
gr.canDelete()    // Can current user delete this record?
gr.canCreate()    // Can current user create records in this table?

// Check field-level permissions
gr.canRead('field_name')   // Can user read this field?
gr.canWrite('field_name')  // Can user write to this field?

// Check user roles
gs.hasRole('admin')              // Does user have admin role?
gs.hasRole('itil')               // Does user have ITIL role?
gs.getUser().isMemberOf('group_sys_id')  // Is user in group?

// Example: Role-based query restriction
var gr = new GlideRecord('incident');
if (!gs.hasRole('admin')) {
    // Non-admins only see their own incidents
    gr.addQuery('caller_id', gs.getUserID());
}
gr.query();

Bypassing Security (Use with Caution)

Sometimes system operations need to bypass security controls. Use these methods sparingly and only in trusted server-side code:

// Bypass Business Rules (not security, but related)
gr.setWorkflow(false);  // Disables Business Rules
gr.autoSysFields(false);  // Disables sys_updated_on, sys_updated_by updates

// Run as system (bypasses ACLs in certain contexts)
// Only works in specific contexts like Scheduled Jobs
var gr = new GlideRecord('sys_user');
gr.addQuery('user_name', 'admin');
gr.query();  // In Scheduled Job context, runs with elevated privileges

// setAbortAction() - Cancel current Business Rule action
// Does NOT bypass security, but controls workflow
current.setAbortAction(true);
Important: setWorkflow(false) disables Business Rules but does NOT bypass ACLs. For ACL bypass, the script context matters - Scheduled Jobs and certain system scripts run with elevated privileges automatically.

Scoped Application Security

In scoped applications, additional security considerations apply:

// Scoped apps have restricted table access by default
// Check Application Registry > Tables for cross-scope access

// To access global tables from a scoped app:
// 1. Table must have "Accessible from" set to "All application scopes"
// 2. OR create an Application Cross-Scope Access record

// Example: Checking scope access programmatically
var scopeCheck = new GlideRecord('sys_scope_privilege');
scopeCheck.addQuery('source_scope', gs.getCurrentScopeSysId());
scopeCheck.addQuery('target_scope', 'global');
scopeCheck.query();
if (scopeCheck.next()) {
    gs.info('Cross-scope access configured');
}

Security Best Practices

Quick Rule: If the code runs in response to user action (UI Action, Client Script via GlideAjax, REST API), use GlideRecordSecure. If it's a system-level background job with no user context, GlideRecord may be appropriate.
Explore More ServiceNow Content

Looking for more ServiceNow tutorials, guides, and best practices? Check out all my ServiceNow articles at jitendrazaa.com/blog/category/servicenow/

11 Frequently Asked Questions

GlideRecord is ServiceNow's proprietary JavaScript API for performing database operations without writing SQL queries. It allows developers to create, read, update, and delete records in ServiceNow tables using server-side scripts like Business Rules, Script Includes, and Scheduled Jobs.

addQuery() accepts individual field-operator-value parameters and can be chained for multiple conditions. addEncodedQuery() accepts a complete query string (like 'priority=1^state=2') copied from the ServiceNow filter builder. Both perform identically in execution time, but addEncodedQuery() is easier for complex queries with multiple OR conditions and is more maintainable when copied from the UI.

gr.field_name returns a pointer to the current record's field, while getValue('field_name') returns a snapshot of the value. If you push gr.sys_id to an array in a loop, all entries will contain the last record's sys_id because the pointer updates with each iteration. Always use getValue() when storing values for later use.

Use setLimit() to restrict returned records, query on indexed fields (sys_id, number, active), use GlideAggregate for counting instead of looping, avoid complex NQ (new query) conditions, and use chooseWindow() for pagination instead of large offsets. Never query without conditions on large tables, and always validate field names to prevent full table scans.

While technically possible, client-side GlideRecord is deprecated and strongly discouraged. It causes synchronous network calls that freeze the browser and creates poor user experience. Use GlideAjax to call server-side Script Includes, which provides asynchronous execution, better security, and improved performance.

GlideRecordSecure automatically enforces Access Control Lists (ACLs) on every operation, while GlideRecord does not. With GlideRecord, you must manually check permissions using canRead(), canWrite(), and canDelete(). GlideRecordSecure returns NULL for fields the user cannot access and blocks writes to restricted fields. Use GlideRecordSecure for user-facing features and sensitive data; use GlideRecord only for system-level operations where you intentionally need to bypass ACLs.

autoSysFields() controls whether ServiceNow automatically updates system fields during insert and update operations. These system fields include:

  • sys_created_by – User who created the record
  • sys_created_on – Timestamp when record was created
  • sys_updated_by – User who last modified the record
  • sys_updated_on – Timestamp of last modification
  • sys_mod_count – Number of times record has been modified

By default, these fields are auto-populated. Calling gr.autoSysFields(false) before insert() or update() prevents this automatic update. This is essential for:

  • Data migrations – Preserving original creation/modification timestamps
  • Import operations – Maintaining the original audit trail from source systems
  • Bulk updates – Preventing "last modified" changes when making administrative updates

GlideAjax is ServiceNow's client-side API for making asynchronous calls to server-side Script Includes. Use it in Client Scripts, UI Policies, or UI Actions when you need server data without blocking the browser. It replaces deprecated synchronous client-side GlideRecord with non-blocking AJAX requests, providing better security (server logic hidden from client) and improved performance.

GlideForm (g_form) is ServiceNow's client-side API for form manipulation, available in Client Scripts, UI Policies, and UI Actions. Key methods include: getValue()/setValue() for field values, setDisplay() to show/hide fields, setMandatory()/setReadOnly() for field properties, addInfoMessage()/addErrorMessage() for user feedback, and getReference() for async reference lookups.

12 Abbreviations & Glossary

Abbreviations & Glossary

Reference guide for technical terms and abbreviations used throughout this article.

ACL - Access Control List
API - Application Programming Interface
CRUD - Create, Read, Update, Delete
ES5 - ECMAScript 5 (JavaScript version)
GlideRecord - ServiceNow's database access API
GlideAggregate - API for aggregate operations (COUNT, SUM)
GlideAjax - Client-to-server async communication
gs - GlideSystem (server-side utilities)
NQ - New Query (OR group in encoded queries)
SQL - Structured Query Language
sys_id - System ID (unique record identifier)
UI - User Interface
URL - Uniform Resource Locator
Link copied to clipboard!
Previous Post
Salesforce Business Rules Engine Guide
Next Post
ServiceNow Business Rules: Complete Developer Guide | From Beginner to Advanced
Archives by Year
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

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