Mastering GlideRecord Queries: Your Gateway to ServiceNow Data
Hey there, fellow ServiceNow enthusiast! Ever wondered how those intricate scripts behind the scenes talk to your database, fetch just the right records, or even create new ones, all without you ever typing a single line of SQL? If you’ve spent any time customizing ServiceNow, you’ve undoubtedly stumbled upon the magic (and sometimes mystery) of GlideRecord. It’s more than just a class; it’s the very bedrock of server-side scripting in ServiceNow, your primary tool for manipulating data.
In this deep dive, we’re going to pull back the curtain on GlideRecord queries. We’ll explore its inner workings, demystify its methods, and equip you with the knowledge to wield it like a pro. Whether you’re a seasoned developer looking to refine your skills or a budding admin taking your first steps into scripting, understanding GlideRecord is non-negotiable. So, grab your favorite beverage, and let’s unravel the power of GlideRecord!
The Grand Stage: Glide API Overview
Before we spotlight GlideRecord, let’s briefly acknowledge the broader context: the Glide API. Think of the Glide API as ServiceNow’s comprehensive toolkit for developers. It’s a vast collection of JavaScript classes and methods designed to interact with almost every aspect of the platform programmatically. From managing users and groups with GlideUser and GlideGroup, to handling dates with GlideDateTime, and even sending notifications with GlideEmail, the Glide API provides the flexibility to customize and extend ServiceNow’s out-of-the-box functionality.
What makes it truly special, particularly for database operations, is its ability to abstract away the complexities of direct SQL queries. Instead of writing database-specific SQL that might vary across different database systems or require intricate security considerations, ServiceNow gives us Glide APIs. These APIs translate your JavaScript instructions into the appropriate database commands, ensuring platform compatibility, security, and performance. And among these powerful APIs, GlideRecord stands tall as the undisputed champion for data interaction.
What is GlideRecord? The Heart of Server-Side Scripting
If ServiceNow scripting were a body, GlideRecord would be its heart, constantly pumping data in and out. It’s an indispensable JavaScript class, native to the ServiceNow platform, and crucially, it runs exclusively on the server-side. This means you’ll encounter it in Business Rules, Script Includes, Workflow Scripts, UI Actions (server-side scripts), Fix Scripts, and, of course, the ever-useful Script – Background module for testing.
At its core, GlideRecord is designed to perform CRUD operations – Create, Read, Update, and Delete – on records within your ServiceNow database tables. Imagine wanting to fetch all active incidents, update a user’s email, or even delete old change requests. Without GlideRecord, you’d be stuck. It acts as an object-oriented wrapper around the database, allowing you to manipulate data using familiar JavaScript syntax instead of wrestling with SQL.
Why Can’t We Just Use SQL?
That’s a common question, and it boils down to several key reasons:
- Abstraction & Portability: ServiceNow runs on various database systems (MySQL, Oracle, etc.). GlideRecord handles the translation, so your script works regardless of the underlying database.
- Security: GlideRecord automatically respects ServiceNow’s Access Control Lists (ACLs). This means if a user doesn’t have permission to read a record via the UI, your server-side GlideRecord script (running in the context of that user) will also respect that restriction. Direct SQL bypasses this critical security layer.
- Efficiency & Platform Integration: GlideRecord is optimized for the ServiceNow platform, leveraging its indexing, caching, and other performance features. It also integrates seamlessly with other platform functionalities like auditing and business rules.
A Stern Word of Caution (and why it’s interview gold): This is a frequently asked question in interviews, and for good reason! Always, and I mean *always*, test your GlideRecord scripts on a non-production instance first. An incorrectly constructed query, like a typo in a field name or an invalid operator, can lead to unexpected behavior. Using insert(), update(), or deleteRecord() on an invalid query result set can lead to irreversible data loss or corruption. Don’t learn this the hard way!
Key Characteristics of GlideRecord:
- Most Common API: You’ll see it everywhere.
- Server-Side Execution: It lives and breathes on the ServiceNow server.
- SQL Generation: Your JavaScript gets converted into optimized SQL under the hood.
- CRUD Powerhouse: Your go-to for all data manipulation.
GlideRecord Architecture: The Abstraction Bridge
Think of GlideRecord as a sophisticated interpreter or a bridge. On one side, you have your familiar JavaScript code. On the other, the complex world of the database, speaking SQL. GlideRecord sits in the middle, taking your high-level JavaScript commands and translating them into precise SQL queries that the database can understand. It then takes the database’s response and presents it back to your script in a usable JavaScript object format.
This abstraction means you, the developer, don’t need to be a SQL expert to interact with the database effectively. You focus on what data you need or what changes you want to make, and GlideRecord handles the how, ensuring consistency and adherence to platform rules.
JavaScript (your script) --> GlideRecord (interprets) --> SQL (generated) --> Database (executes) --> Results (back to GlideRecord) --> JavaScript Objects (for your script)
Your GlideRecord Toolkit: Essential Methods
GlideRecord comes packed with a plethora of methods, each serving a specific purpose. We’ll categorize them for clarity and walk through practical examples. Remember to use the Script – Background application (navigate to System Definition > Scripts - Background) to test these exercises, and use gs.info() for output in modern ServiceNow environments (gs.print() also works but gs.info() is generally preferred as it logs to the System Log with an info level).
1. Basic Output and Scripting Essentials
Before diving into GlideRecord, let’s just ensure we know how to see our script’s output.
gs.info()/gs.print(): Displaying Messages
These methods are your debugging buddies, printing messages to the console or system logs.
gs.info('Welcome to ServiceNow Academy');
// Result → Welcome to ServiceNow Academy (in System Logs)
var a = 10;
var b = 20;
var c = a + b;
gs.info(a + b);
// Result → 302. The Core Query Loop: new GlideRecord(), query(), next()
This is the fundamental pattern you’ll use to retrieve records.
new GlideRecord('table_name'): Instantiating GlideRecord
This creates a new GlideRecord object, associating it with a specific table.
query(): Executing the Query
This method executes the query against the database based on any conditions you’ve added. It doesn’t return the results directly but prepares them for iteration.
while (gr.next()): Iterating Through Results
After query(), you use next() to move to the first record, and then subsequent records. The while loop continues as long as there are more records to process.
var inc = new GlideRecord('incident'); // Create a GlideRecord object for the incident table
inc.query(); // Execute the query (no conditions, so all incidents)
while (inc.next()) { // Loop through each incident record found
gs.info(inc.number); // Print the incident number
}Result → Prints the number of every incident record in the system.
Interview Relevance: This exact pattern is fundamental. Be prepared to explain query() vs. next().
3. Refining Your Searches: Adding Query Conditions
Retrieving *all* records is rarely what you want. These methods help you filter your results.
addQuery('field_name', 'value'): Simple Conditions
This is your bread-and-butter for adding a single WHERE clause. By default, it uses an = (equals) operator.
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1'); // Find incidents where priority is '1' (Critical)
inc.query();
while (inc.next()) {
gs.info(inc.number + ' - Priority: ' + inc.priority.getDisplayValue());
}Result → Prints incident numbers and their priority (display value) for all critical priority tickets.
- Multiple
addQuery()Calls: Logical AND
- Multiple
When you use multiple addQuery() methods, they are implicitly joined with a logical AND.
var inc = new GlideRecord('incident');
inc.addQuery('active', true); // Condition 1: Active incidents
inc.addQuery('priority', '1'); // Condition 2: Priority 1
inc.addQuery('category', 'software'); // Condition 3: Category is 'software'
inc.query();
while (inc.next()) {
gs.info(inc.number + ' - ' + inc.short_description);
}Result → Prints incident numbers and short descriptions for active, priority 1, software-category incidents.
addQuery('field_name', 'operator', 'value'): Advanced Operators
GlideRecord supports various comparison operators, mimicking SQL functionality:
Operators: `=`, `!=`, `>`, `>=`, `<`, `<=`, `IN`, `NOT IN`, `STARTSWITH`, `ENDSWITH`, `CONTAINS`, `DOES NOT CONTAIN`.
// Get Active incidents with priority less than or equal to 2 (Critical or High)
var inc = new GlideRecord('incident');
inc.addActiveQuery(); // Shortcut for 'active', true
inc.addQuery('priority', '<=', '2');
inc.query();
while (inc.next()) {
gs.info(inc.number + ' - Priority: ' + inc.priority.getDisplayValue());
}
// Result → Prints Critical (1) and High (2) priority tickets.
// Working with CONTAINS
var inc2 = new GlideRecord('incident');
inc2.addActiveQuery();
inc2.addQuery('priority', '<=', '2');
inc2.addQuery('short_description', 'CONTAINS', 'SAP');
inc2.query();
while (inc2.next()) {
gs.info(inc2.number + ' - ' + inc2.short_description);
}
// Result → Prints records where priority <=2 AND short_description contains 'SAP'.
// Working with IN operator
var categories = ['software', 'hardware'];
var inc3 = new GlideRecord('incident');
inc3.addQuery('category', 'IN', categories); // Find incidents where category is 'software' OR 'hardware'
inc3.query();
while (inc3.next()) {
gs.info(inc3.getValue('number') + ' - ' + inc3.getValue('category'));
}
// Result → Prints incidents categorized as Software or Hardware.Troubleshooting: Misspelling operators or field names is common. Ensure the `value` for comparison matches the field’s data type (e.g., a string ‘1’ for priority, not a number 1, unless the field is purely numeric).
addEncodedQuery('encoded_query_string'): Complex Queries Made Easy
For more complex conditions involving OR logic or many AND clauses, building an encoded query string is often cleaner than multiple addQuery() calls. You can generate these directly from any list view in ServiceNow:
- Navigate to the desired list (e.g., Incident list).
- Apply your conditions using the filter builder (e.g.,
Active = true AND Priority = 1 OR Category = Software). - Right-click the filter breadcrumbs and select “Copy query”.
- Paste this string directly into
addEncodedQuery().
var encodedQueryString = 'active=true^category=software^priority=1';
var inc = new GlideRecord('incident');
inc.addEncodedQuery(encodedQueryString);
inc.query();
while (inc.next()) {
gs.info(inc.number + ' - ' + inc.short_description);
}Result → Prints records matching the complex encoded query string.
Interview Relevance: A strong candidate can explain the difference and use cases for `addQuery` and `addEncodedQuery`.
addActiveQuery()/addInactiveQuery(): Convenience for Status
These are handy shortcuts for addQuery('active', true) and addQuery('active', false) respectively.
var incActive = new GlideRecord('incident');
incActive.addActiveQuery(); // Equivalent to incActive.addQuery('active', true);
incActive.addQuery('priority', '1');
incActive.query();
gs.info('Active Priority 1 Incidents: ' + incActive.getRowCount());
var incInactive = new GlideRecord('incident');
incInactive.addInactiveQuery(); // Equivalent to incInactive.addQuery('active', false);
incInactive.addQuery('priority', '1');
incInactive.query();
gs.info('Inactive Priority 1 Incidents: ' + incInactive.getRowCount());Result → Counts and prints active/inactive priority 1 incidents.
4. Manipulating and Inspecting Query Results
Once you have your results, these methods help you interact with them.
orderBy('field_name')/orderByDesc('field_name'): Sorting Results
Order your results ascending or descending based on a field. Useful for presentation or specific logic.
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.addQuery('category', 'software');
inc.orderBy('short_description'); // Ascending order by Short Description
inc.query();
gs.info('Incidents (ordered ascending by short description):');
while (inc.next()) {
gs.info(inc.number + ' - ' + inc.short_description);
}
var incDesc = new GlideRecord('incident');
incDesc.addQuery('priority', '1');
incDesc.addQuery('category', 'software');
incDesc.orderByDesc('short_description'); // Descending order
incDesc.query();
gs.info('Incidents (ordered descending by short description):');
while (incDesc.next()) {
gs.info(incDesc.number + ' - ' + incDesc.short_description);
}Result → Prints incidents sorted by short description, first ascending, then descending.
setLimit(number): Limiting Records (Performance Booster!)
Crucial for performance! If you only need a few records, don’t fetch hundreds. Always use setLimit() when possible.
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.orderByDesc('sys_created_on'); // Get the 10 most recently created priority 1 incidents
inc.setLimit(10);
inc.query();
gs.info('Top 10 latest Priority 1 Incidents:');
while (inc.next()) {
gs.info(inc.number + ' - ' + inc.short_description);
}Result → Prints only the latest 10 records matching the criteria.
Best Practice: Always consider setLimit() when dealing with potentially large result sets, especially in UI scripts or frequently run business rules. It’s a key performance optimization.
get('field_name', 'value')orget('sys_id'): Retrieving a Single Record
If you know a unique identifier (like `sys_id` or `number`), `get()` is the fastest way to fetch a single record without a loop.
// Get record by number
var incByNumber = new GlideRecord('incident');
incByNumber.get('number', 'INC0009005'); // Assuming this incident exists
gs.info('Sys_ID for INC0009005: ' + incByNumber.sys_id);
// Get record by sys_id (common pattern)
var incBySysId = new GlideRecord('incident');
incBySysId.get('a95d5a7d5300220002c6435723a1ef90'); // Replace with a valid sys_id from your instance
gs.info('Incident number for provided Sys_ID: ' + incBySysId.number);Result → Prints the requested information for the single record.
chooseWindow(start_index, end_index): Paging Through Results
Similar to setLimit but allows you to fetch a “window” of records. The first number is included, the second is excluded.
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.addActiveQuery();
inc.chooseWindow(3, 7); // Get records from index 3 (4th record) up to (but not including) index 7
inc.query();
gs.info('Incidents from index 3 to 6 (4 records):');
while (inc.next()) {
gs.info(inc.number);
}Result → Prints the 4th, 5th, 6th, and 7th records of the query result.
getRowCount(): Counting Records
Returns the total number of records that matched your query.
var inc = new GlideRecord('incident');
inc.query();
gs.info('Total incidents in the system: ' + inc.getRowCount());
var activeUsers = new GlideRecord('sys_user');
activeUsers.addQuery('active', true);
activeUsers.query();
gs.info('Total active users: ' + activeUsers.getRowCount());Result → Prints the count of records.
Performance Note: For very large tables, `getRowCount()` can sometimes be slow as it might have to count all matching records. If you only need to check if *any* records exist, `gr.query(); if (gr.next())` is often faster.
getValue('field_name')/getDisplayValue('field_name'): Raw vs. Display Values
getValue() returns the actual stored database value (e.g., ‘1’ for Critical priority, sys_id for reference fields). getDisplayValue() returns the user-friendly, display-ready value (e.g., ‘Critical’, user’s name).
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.setLimit(1); // Just get one for example
inc.query();
if (inc.next()) {
gs.info('Priority (Raw Value): ' + inc.getValue('priority'));
gs.info('Priority (Display Value): ' + inc.getDisplayValue('priority')); // Or inc.priority.getDisplayValue()
}Result → Prints both the raw (e.g., ‘1’) and display (e.g., ‘Critical’) values.
hasNext(): Check for More Elements
Returns `true` if there are more records to iterate through. Useful for conditional logic after a query without immediately calling `next()`.
var inc = new GlideRecord('incident');
inc.query();
gs.info('Are there any incidents? ' + inc.hasNext());Result → Prints `true` if any incidents exist, `false` otherwise.
getUniqueValue(): Getting the Record’s sys_id
A convenient way to get the unique identifier (sys_id) of the current record.
var inc = new GlideRecord('incident');
inc.query();
if (inc.next()) { // Move to the first record
var uniqueValue = inc.getUniqueValue();
gs.info('First incident sys_id: ' + uniqueValue);
}getTableName()/getRecordClassName(): Identifying the Table
Both methods return the name of the table the GlideRecord object is associated with.
var gr = new GlideRecord('change_request');
gs.info('Table name (getTableName): ' + gr.getTableName());
gs.info('Table name (getRecordClassName): ' + gr.getRecordClassName());Result → Prints ‘change_request’.
getEncodedQuery(): Retrieving the Generated Query String
Useful for debugging to see the actual encoded query string generated by your `addQuery()` calls.
var inc = new GlideRecord('incident');
inc.addActiveQuery();
inc.addQuery('priority', '1');
inc.addQuery('category', 'software');
inc.query();
// Call getEncodedQuery() *after* adding conditions but *before* the loop starts
// or inside the loop if you want to see the *current* query string.
// Note: It returns the query conditions, not the result of the query itself.
gs.info('Generated Encoded Query: ' + inc.getEncodedQuery());Result → Prints the encoded query string, e.g., `active=true^priority=1^category=software`.
5. CRUD Operations: Modifying Data
These are the methods for creating, updating, and deleting records.
initialize()&insert(): Creating New Records
initialize() prepares a new, empty record object, filling it with default values. insert() saves the record to the database.
var inc = new GlideRecord('incident');
inc.initialize(); // Initialize a new incident record
inc.category = 'network'; // Set field values directly
inc.short_description = 'Firewall Issue - Urgent';
inc.priority = 1; // Note: Use the raw value '1' for priority, not 'Critical'
inc.insert(); // Save the new record to the database
gs.info('New Incident created: ' + inc.number);Result → Creates a new incident record and prints its number.
Alternative: setValue('field_name', 'value')
setValue() is often preferred for setting field values, especially when field names might be dynamic or to ensure type safety.
var inc2 = new GlideRecord('incident');
inc2.initialize();
inc2.setValue('category', 'hardware');
inc2.setValue('short_description', 'Critical server outage');
inc2.insert();
gs.info('New Incident (using setValue): ' + inc2.number);update(): Updating a Single Record
After fetching a record, modify its fields, then call `update()` to save changes.
var inc = new GlideRecord('incident');
inc.get('number', 'INC0000057'); // Get a specific incident (replace with a real incident number)
if (inc.isValidRecord()) { // Always validate you found a record before updating!
inc.state = '2'; // Set state to 'In Progress' (use the raw value)
inc.update();
gs.info('Incident ' + inc.number + ' updated to state: ' + inc.getDisplayValue('state'));
} else {
gs.warn('Incident INC0000057 not found for update.');
}Result → Updates the specified incident record.
updateMultiple(): Updating Multiple Records
A powerful method to update all records matching your query conditions in one go. Be extremely careful with this!
var inc = new GlideRecord('incident');
inc.addQuery('category', 'hardware'); // Find all hardware incidents
inc.setValue('category', 'software'); // Change their category to software
inc.updateMultiple(); // Perform the update
gs.info('All hardware incidents categories updated to software (check incident list).');Result → Updates all records where category was ‘hardware’ to ‘software’.
Troubleshooting/Warning: Always run `updateMultiple()` with `setWorkflow(false)` and `autoSysFields(false)` if you intend to bypass Business Rules and not update system fields for mass updates. Test *thoroughly* in non-production.
deleteRecord(): Deleting a Single Record
Deletes the current record the GlideRecord object is pointing to.
var inc = new GlideRecord('incident');
inc.get('number', 'INC0010013'); // Get the record to delete (use a test incident!)
if (inc.isValidRecord()) {
var deletedNumber = inc.number;
inc.deleteRecord();
gs.info('Incident ' + deletedNumber + ' deleted.');
} else {
gs.warn('Incident INC0010013 not found for deletion.');
}Result → Deletes the specified incident.
deleteMultiple(): Deleting Multiple Records
Deletes all records that satisfy the query conditions. Use with extreme caution!
var inc = new GlideRecord('incident');
inc.addQuery('priority', '4'); // Find all priority 4 incidents (usually low priority test data)
inc.query();
var countToDelete = inc.getRowCount();
if (countToDelete > 0) {
inc.deleteMultiple();
gs.info(countToDelete + ' Priority 4 incidents deleted.');
} else {
gs.info('No Priority 4 incidents found to delete.');
}Result → Deletes all records matching the query.
Warning: Always perform a `getRowCount()` and potentially a `setLimit()` before running `deleteMultiple()` on production-like data. It’s incredibly easy to delete more than you intended.
6. Access Control and System Behavior Controls
GlideRecord also offers methods to check permissions and control how the platform reacts to your changes.
canCreate(),canRead(),canWrite(),canDelete(): ACL Checks
These methods respect ACLs and return `true` if the current user (or the user impersonated by the script) has the necessary permissions. This is crucial for building secure applications.
var inc = new GlideRecord('incident');
gs.info('Can current user create incident records? ' + inc.canCreate());
// Get an existing incident for read/write/delete checks
inc.get('some_valid_sys_id'); // Replace with a real sys_id
if (inc.isValidRecord()) {
gs.info('Can current user read this incident? ' + inc.canRead());
gs.info('Can current user write to this incident? ' + inc.canWrite());
gs.info('Can current user delete this incident? ' + inc.canDelete());
}Result → Prints boolean values indicating access permissions.
autoSysFields(boolean)&setWorkflow(boolean): Controlling Platform Automation
These advanced methods give you granular control:
autoSysFields(false): Prevents the automatic update of system fields like `sys_updated_on`, `sys_updated_by`, `sys_mod_count` during an `update()` operation. Useful for data imports or specific integrations where you don’t want to affect historical metrics.setWorkflow(false): Prevents Business Rules from running on the record during an `update()` or `insert()` operation. Extremely powerful but must be used with caution, as it can bypass critical logic.
Note: autoSysFields() might behave differently or be unavailable in scoped applications, requiring alternative approaches.
var inc = new GlideRecord('incident');
inc.addQuery('state', '1'); // Find incidents in 'New' state
inc.setLimit(5); // Limit for safety
inc.query();
gs.info('Updating 5 incidents without triggering business rules or modifying system fields...');
while (inc.next()) {
inc.autoSysFields(false); // Do not update sys_updated_on, sys_updated_by etc.
inc.setWorkflow(false); // Do not run business rules
inc.setValue('state', '2'); // Set state to 'In Progress'
inc.update();
gs.info('Updated ' + inc.number);
}Result → Updates records silently, bypassing typical platform automation. Verify the `sys_updated_on` and `sys_mod_count` fields on these records.
addNullQuery('field_name')/addNotNullQuery('field_name'): Handling Empty Fields
Query for records where a specific field is empty or has a value.
var incNullSD = new GlideRecord('incident');
incNullSD.addNullQuery('short_description'); // Incidents with empty Short Description
incNullSD.setLimit(5);
incNullSD.query();
gs.info('Incidents with null Short Description:');
while (incNullSD.next()) {
gs.info(incNullSD.number);
}
var incNotNullSD = new GlideRecord('incident');
incNotNullSD.addNotNullQuery('short_description'); // Incidents with non-empty Short Description
incNotNullSD.setLimit(5);
incNotNullSD.query();
gs.info('Incidents with not null Short Description:');
while (incNotNullSD.next()) {
gs.info(incNotNullSD.number);
}Result → Prints incident numbers based on whether their short description is null or not.
7. Advanced / Less Common Methods
addJoinQuery('joined_table', 'local_field', 'remote_field'): Joining Tables
Allows you to perform a SQL JOIN-like operation. The query is still performed on the primary GlideRecord table, but results are filtered based on the joined table’s conditions.
// Example: Find problems opened by a user who also has incidents where they are the caller.
var prob = new GlideRecord('problem');
// This joins the 'problem' table (prob.opened_by) with 'incident' (inc.caller_id)
// It will only return problem records where the problem's 'opened_by' user sys_id
// also exists as a 'caller_id' on *any* incident record.
prob.addJoinQuery('incident', 'opened_by', 'caller_id');
prob.query();
gs.info('Problems where the opener also called an incident:');
while (prob.next()) {
gs.info(prob.number + ' - Opened By: ' + prob.opened_by.name);
}Result → Displays problem records where the `opened_by` user is also a `caller_id` on at least one incident.
Interview Relevance: `addJoinQuery` is a more advanced topic. Understanding its purpose and how it works (filtering the primary GR, not fetching fields from the joined table directly) is a strong indicator of deeper knowledge.
getLink(boolean)&gs.getProperty('glide.servlet.uri'): Generating Record Links
Get a direct URL to the current record. The `gs.getProperty(‘glide.servlet.uri’)` fetches the base URL of your ServiceNow instance.
var inc = new GlideRecord('incident');
inc.addActiveQuery();
inc.addQuery('category', 'software');
inc.addQuery('priority', '1');
inc.setLimit(1);
inc.query();
if (inc.next()) {
var recordLink = gs.getProperty('glide.servlet.uri') + inc.getLink(false); // 'false' for UI16 link
gs.info('Link to incident ' + inc.number + ': ' + recordLink);
}Result → Returns a clickable URL to the record.
isNewRecord()/newRecord(): Checking Record State / Preparing a New Record
newRecord() is similar to `initialize()` but also assigns a unique `sys_id` to the new record object immediately. `isNewRecord()` checks if the current GlideRecord object represents a record that hasn’t been saved to the database yet.
var inc = new GlideRecord('incident');
inc.newRecord(); // Creates a new record object and assigns a sys_id
gs.info('Is this a new record before insert? ' + inc.isNewRecord()); // Should be true
inc.short_description = 'Test newRecord method';
inc.insert();
gs.info('New record number: ' + inc.number);
gs.info('Is this a new record after insert? ' + inc.isNewRecord()); // Should be falseResult → Creates a new record and demonstrates the `isNewRecord` status.
isValid()/isValidField()/isValidRecord(): Validation
These methods help ensure you’re working with valid tables, fields, and records.
// isValid() - checks if the table exists
var validGR = new GlideRecord('incident');
gs.info('Does "incident" table exist? ' + validGR.isValid()); // True
var invalidGR = new GlideRecord('non_existent_table');
gs.info('Does "non_existent_table" exist? ' + invalidGR.isValid()); // False
// isValidField() - checks if a field exists on the table
var inc = new GlideRecord('incident');
gs.info('Does "incident" table have "category" field? ' + inc.isValidField('category')); // True
gs.info('Does "incident" table have "my_custom_field" field? ' + inc.isValidField('my_custom_field')); // False
// isValidRecord() - checks if the current GR object holds a valid record (after get() or next())
var incValid = new GlideRecord('incident');
incValid.get('number', 'INC0000057'); // Get an existing record
gs.info('Is INC0000057 a valid record? ' + incValid.isValidRecord()); // True
var incInvalid = new GlideRecord('incident');
incInvalid.get('number', 'NONEXISTENTINC'); // Try to get a non-existent record
gs.info('Is NONEXISTENTINC a valid record? ' + incInvalid.isValidRecord()); // FalseResult → Prints boolean values for existence and validity checks.
getElement('field_name'): Accessing a Field’s GlideElement Object
Returns a GlideElement object for a specific field. This allows access to field-specific methods like `getDisplayValue()`, `nil()`, `hasValue()`, `setLabel()`, `getGlideObject()`, etc.
var inc = new GlideRecord('incident');
inc.initialize();
inc.setValue('short_description', 'I am facing VPN Problem');
inc.insert();
var shortDescriptionElement = inc.getElement('short_description');
gs.info('Short description element value: ' + shortDescriptionElement.getValue());
gs.info('Is short_description empty? ' + shortDescriptionElement.nil());Result → Prints the field value and its `nil()` status.
getGlideObject()/getNumericValue()/setAbortAction(true): Advanced Date Comparison Example
This snippet (often seen in Business Rules) demonstrates how to compare dates and prevent an action.
// This example typically runs in a Business Rule on a table with u_date1 and u_date2 fields.
// Assuming 'current' is the GlideRecord of the record being acted upon.
// For Script - Background testing, you'd need to mock 'current'.
// Let's create a simplified version for demonstration:
var grMock = new GlideRecord('incident'); // Using incident for demo, imagine it has u_date1, u_date2
grMock.u_date1 = '2023-01-10 10:00:00'; // Example start date
grMock.u_date2 = '2023-01-05 10:00:00'; // Example end date (before start, will trigger error)
if ((!grMock.u_date1.nil()) && (!grMock.u_date2.nil())) { // Check if dates are not empty
var start = grMock.u_date1.getGlideObject().getNumericValue(); // Get date as milliseconds since epoch
var end = grMock.u_date2.getGlideObject().getNumericValue();
if (start > end) {
gs.addInfoMessage('Start date must be before end date.'); // Message to user
// grMock.u_date1.setError('Start must be before end'); // Set field-specific error (works in BR context)
// grMock.setAbortAction(true); // Prevent the current action (insert/update) (works in BR context)
gs.info('Validation failed: Start date (' + grMock.u_date1 + ') is after End date (' + grMock.u_date2 + ').');
} else {
gs.info('Validation passed: Start date is before End date.');
}
}Result → In a Business Rule, it would prevent the record from saving and show an error. Here, it logs the validation outcome.
Troubleshooting Common GlideRecord Issues
Even seasoned developers hit snags. Here are a few common pitfalls and how to navigate them:
- Typos in Field Names: A classic! `gr.addQuery(‘short_descriptoin’, ‘…’);` will likely result in an empty query. Always double-check field names against the table dictionary. `isValidField()` can help.
- Incorrect Operators or Values: Using `addQuery(‘active’, 1)` instead of `addQuery(‘active’, true)` for a boolean field, or `addQuery(‘priority’, ‘=’, 1)` when `addQuery(‘priority’, ‘1’)` would suffice, can cause issues. Ensure data types match and use string values for operators where required.
- Case Sensitivity: Field names are case-sensitive (e.g., `short_description` vs. `Short_Description`). String values in queries might or might not be case-sensitive depending on the field type and database configuration, but generally, assume they are for consistency.
- No `query()` or `next()`: Forgetting `gr.query()` means no data is fetched. Forgetting `while (gr.next())` means your script won’t iterate through results (or won’t even process the first one if you only use `if (gr.next())`).
- Infinite Loops: A rare but scary one. If your loop condition is flawed (e.g., `while (true)` or `while (gr)` instead of `while (gr.next())`), you might crash your instance.
- Accidental Mass Updates/Deletes: The most dangerous. Always use `setLimit()` during testing. Review your `addQuery()` conditions meticulously for `updateMultiple()` and `deleteMultiple()`. A `gs.info(gr.getEncodedQuery())` before execution can be a lifesaver.
- ACL Violations: Your script runs in a specific user context. If that user doesn’t have read/write permissions via ACLs, your GlideRecord operations will fail silently or return no results. Check `canRead()`, `canWrite()`, etc., or use `gs.currentSession.setStrictQuery(false)` (with extreme caution and understanding of implications) for system-level scripts that need to bypass user ACLs.
- Performance Issues: Queries without `addQuery()` conditions on large tables, or fetching thousands of records without `setLimit()`, can significantly impact performance. Filter early, filter often.
Conclusion: The Power is Yours
GlideRecord is undeniably the cornerstone of server-side scripting in ServiceNow. It’s your reliable workhorse for interacting with the database, performing everything from simple data retrieval to complex CRUD operations. By understanding its architecture, mastering its methods, and adhering to best practices, you unlock a tremendous amount of power to customize and extend your ServiceNow instance.
Remember, practice makes perfect! The more you experiment with these methods in your personal developer instance, the more intuitive they’ll become. Always test thoroughly, be mindful of performance, and never underestimate the importance of security. With GlideRecord in your arsenal, you’re well on your way to becoming a ServiceNow scripting wizard!