Master database operations with encoded queries, OR conditions, and performance optimization techniques
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);
}
GlideRecord is predominantly used in server-side scripts. As explained in the ServiceNow Community developer articles, the primary use cases include:
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.
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 |
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.
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');
}
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.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);
}
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();
}
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 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!
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.GlideRecord provides multiple methods for building queries. Understanding the nuances of each method is crucial for writing efficient and correct code.
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
| 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') |
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);
}
// 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();
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.
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 |
// 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();
As described in the ServiceNow Scripting 101 guide, the easiest way to generate encoded queries is through the ServiceNow UI:
Go to the list view of the table you want to query (e.g., incident.list)
Use the condition builder to create your filter with all conditions
Click "Run" to validate the query returns expected records
Right-click on the breadcrumb text and select "Copy query"
// 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();
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()');
Building complex queries with OR conditions requires understanding how GlideRecord chains conditions. This is one of the most frequently misunderstood aspects of GlideRecord scripting.
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);
}
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.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
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))
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();
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.
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
setLimit(1) makes a dramatic performance difference. The database stops after finding the first match.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
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);
}
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
| 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 |
Even experienced ServiceNow developers encounter GlideRecord errors. According to the ServiceNow community's hints and tips guide, these are the most common issues.
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
getValue('field_name') when storing values for later use. Using gr.field_name returns a pointer that changes as you iterate through records.// 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);
}
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');
}
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');
}
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
| 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 |
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.
// 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');
// 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');
// 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 };
}
}
// 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();
// 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);
}
// 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'
});
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:
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.
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 |
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!
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);
}
// 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();
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);
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');
}
Looking for more ServiceNow tutorials, guides, and best practices? Check out all my ServiceNow articles at jitendrazaa.com/blog/category/servicenow/
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:
By default, these fields are auto-populated. Calling gr.autoSysFields(false) before insert() or update() prevents this automatic update. This is essential for:
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.
Reference guide for technical terms and abbreviations used throughout this article.