Demystifying GlideRecord: Your Ultimate Guide to ServiceNow Data Manipulation
Hey there, fellow ServiceNow enthusiast! Ever felt like you’re performing magic when interacting with your instance’s database without writing a single line of SQL? That “magic” is most likely powered by something called GlideRecord. If you’re looking to dive deep into server-side scripting, master data operations, and truly elevate your ServiceNow game, understanding GlideRecord isn’t just helpful – it’s absolutely essential.
Think of GlideRecord as your trusted, all-access pass to the underlying data in ServiceNow. It’s the cornerstone of server-side scripting, allowing you to fetch, create, update, and delete records with JavaScript, making your customizations robust and efficient. In this comprehensive guide, we’re going to break down GlideRecord architecture, explore its most vital methods, share practical examples, and equip you with the knowledge to wield its power like a seasoned pro.
Ready to transform your scripting skills? Let’s dive in!
Glide API Overview: The Language of ServiceNow Customization
Before we pinpoint GlideRecord, let’s zoom out a bit and understand the bigger picture: the Glide API. ServiceNow developers frequently leverage Glide APIs to tweak default application behaviors and customize existing functionalities. These Glide Classes are the backbone, offering immense flexibility to interact with your ServiceNow instance through scripting. The beauty of these APIs is that they let you perform complex database operations without the headache of writing direct SQL queries. Each API is packed with methods, each designed to execute specific operations within the platform.
Types of Glide APIs: Client-Side vs. Server-Side
ServiceNow conveniently categorizes its APIs into two main camps based on where they execute:
- Client-Side APIs: These run directly in the user’s web browser. They’re all about enhancing the user experience, manipulating forms, and interacting with UI elements. Examples include
GlideForm(for controlling forms),GlideUser(for user information),GlideAjax(for asynchronous server communication), andGlideDialogWindow. - Server-Side APIs: These execute on the ServiceNow server. They’re your heavy lifters for database interactions, backend logic, integrations, and complex business processes. This is where our star,
GlideRecord, truly shines, alongside others likeGlideSystem,GlideDate,GlideDateTime, andGlideAggregation.
Our focus today is squarely on the server-side, and specifically, the unsung hero that handles all your data needs: GlideRecord.
What is GlideRecord and Why is it Essential for Every ServiceNow Developer?
If you ask any experienced ServiceNow developer about their most frequently used server-side API, GlideRecord will likely be at the top of their list. It’s a special Java class, natively exposed to JavaScript, and it runs exclusively from the server side. Its primary mission? To perform CRUD operations (Create, Read, Update, Delete) on your ServiceNow database tables, all without requiring you to write a single SQL query.
Think of it as a powerful wrapper that translates your JavaScript commands into the necessary SQL statements, handles the database communication, and then presents the results back to you in a usable format. This abstraction is a game-changer because it means you don’t need to be an SQL expert to manage data effectively within ServiceNow.
Here’s why GlideRecord is an absolute must-know:
- Database Interaction Simplified: Directly interacting with the database via raw SQL queries is generally not allowed, or at least, not recommended in ServiceNow. GlideRecord provides the sanctioned, secure, and efficient pathway to do this.
- CRUD Powerhouse: Whether you need to fetch a list of incidents, update a user’s profile, create a new change request, or delete old records, GlideRecord handles all these fundamental database operations with ease.
- Row and Column Savvy: This API is designed to manage both rows (individual records) and columns (fields) in the underlying database, giving you granular control over your data.
- Server-Side Execution: Being server-side means it’s perfect for Business Rules, Script Includes, Fix Scripts, UI Actions (server-side scripts), Workflow Scripts, and more, where direct database manipulation is required.
- Most Common API: Its versatility and importance mean you’ll encounter and use it constantly in your ServiceNow development journey.
A Critical Note on Testing:
Always, and we mean always, test your GlideRecord queries and operations on a non-production instance first! An incorrectly constructed encoded query, a typo in a field name, or a logical error can lead to invalid queries. Running a bad query with insert(), update(), or especially deleteRecord()/deleteMultiple() can result in unintended data modification or, worse, irreversible data loss in your production environment. Don’t learn this the hard way!
GlideRecord Architecture: Bridging JavaScript and the Database
The “architecture” of GlideRecord isn’t about complex diagrams, but rather the elegant abstraction it provides. At its heart, GlideRecord acts as an Object-Relational Mapper (ORM) for the ServiceNow platform. Instead of you crafting SQL queries like SELECT * FROM incident WHERE active = true AND priority = 1;, you write equivalent JavaScript code using GlideRecord methods.
// Your JavaScript with GlideRecord
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.addQuery('priority', 1);
gr.query();
// What GlideRecord does behind the scenes (simplified):
// 1. Translates your JavaScript into an optimized SQL query.
// 2. Executes the SQL query against the database.
// 3. Maps the SQL result set back into a JavaScript object you can interact with.
This API mapping is what makes GlideRecord so powerful and developer-friendly. It handles the nuances of database drivers, connection pools, and SQL syntax specific to ServiceNow’s underlying database, allowing you to focus on the business logic rather than database intricacies.
Unlocking Data: Common GlideRecord Methods Explained with Practical Examples
Now for the fun part: getting our hands dirty with the actual methods! You’ll typically execute these scripts in a Script – Background application for testing purposes. Just navigate to System Definition > Scripts - Background in your ServiceNow instance.
Basic Output and Scripting Essentials
Before diving into GlideRecord, let’s briefly touch on how to get output from your server-side scripts:
gs.print() and gs.info(): Your Console.log on the Server
These global functions are your best friends for debugging and displaying output in server-side scripts. gs.print() writes directly to the text output, while gs.info() writes to the system logs, often used for more formal logging.
gs.print('Welcome to ServiceNow Academy!');
gs.info('A message logged to the system logs.');
// Result in Script-Background output: Welcome to ServiceNow Academy!
// Result in System Logs: A message logged to the system logs.
Simple arithmetic also works, demonstrating basic JavaScript execution:
var a = 10;
var b = 20;
var c = a + b;
gs.print('The sum is: ' + c);
// Result: The sum is: 30
Querying and Filtering Data
This is where GlideRecord truly shines – finding the data you need!
new GlideRecord('table_name'): Instantiating GlideRecord
This is how you create a GlideRecord object, specifying the table you want to interact with. Think of it as telling ServiceNow, “I want to work with records from THIS table.”
var inc = new GlideRecord('incident'); // Now 'inc' can interact with the Incident table
query(): Execute the Search
After you’ve defined your filters, query() is the method that actually executes the search against the database. Without it, your filters are just definitions!
var inc = new GlideRecord('incident');
inc.query(); // This fetches ALL records from the incident table (use with caution in production!)
while (inc.next()) { // Loop through each found record
gs.print(inc.number + ' - ' + inc.short_description); // Print the incident number and short description
}
// Result: Prints numbers and short descriptions of all incidents
next() and while(gr.next()): Iterating Through Results
After query() returns a result set, next() moves to the next record in that set. When used in a while loop, while(gr.next()) is the standard way to process each record found by your query. It’s how you “walk” through your search results.
addQuery('field_name', 'value'): Simple Filtering
This is your bread and butter for adding conditions. It builds a WHERE clause for your SQL query. You can chain multiple addQuery() calls, and they’ll be combined with an AND operator.
// Exercise - 1: Display priority 1 tickets from the incident table
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1'); // Add the condition: priority equals 1
inc.query();
while (inc.next()) { // Loop through results
gs.print(inc.number + ' - ' + inc.priority.getDisplayValue());
}
// Result: Prints incident numbers and their display priority for all P1 tickets.
addQuery('field_name', 'operator', 'value'): Advanced Filtering
For more complex comparisons (not just equality), you can provide an operator. Common operators include `=`, `!=`, `>`, `>=`, `<`, `<=`, `IN`, `NOT IN`, `STARTSWITH`, `ENDSWITH`, `CONTAINS`, `DOES NOT CONTAIN`, `INSTANCEOF`.
// Exercise - 5: Get Active and Priority is less than or equal to 2
var inc = new GlideRecord('incident');
inc.addActiveQuery(); // A shortcut for active=true
inc.addQuery('priority', '<=', 2); // Priority is 1 (Critical) or 2 (High)
inc.query();
while (inc.next()) {
gs.print(inc.number + ' - ' + inc.priority.getDisplayValue());
}
// Result: Prints Critical (P1) and High (P2) active incident numbers.
// Exercise - 8: Working with IN operator for categories
var categories = ['software', 'hardware'];
var inc = new GlideRecord('incident');
inc.addQuery('category', 'IN', categories); // Category is either 'software' OR 'hardware'
inc.query();
while(inc.next()) {
gs.print(inc.getValue('number') + ' - ' + inc.getValue('category'));
}
// Result: Prints incidents where category is Software or Hardware.
// Exercise - 9: Working with STARTSWITH Operator
var inc = new GlideRecord('incident');
inc.addQuery('category', 'STARTSWITH', 'net'); // Category starts with 'net' (e.g., 'network')
inc.query();
while(inc.next()) {
gs.print(inc.number + ' - ' + inc.category.getDisplayValue());
}
// Result: Prints incidents where the category starts with 'net'.
addEncodedQuery('encoded_query_string'): Power Filtering
This method allows you to use the exact encoded query string you can generate from a list view in ServiceNow. It's incredibly powerful for complex conditions that involve multiple ANDs, ORs, and nested conditions, saving you multiple addQuery() lines.
// Exercise - 3: Using a complex encoded query directly
// Condition from UI: active = true AND priority = 1 AND category = software
var inc = new GlideRecord('incident');
inc.addEncodedQuery('active=true^category=software^priority=1');
inc.query();
while(inc.next()){
gs.print(inc.number);
}
// Result: Prints incident numbers matching all three conditions.
// Exercise - 4: Encoded Query stored in a variable
var ecq = 'active=true^category=software^priority=1';
var inc = new GlideRecord('incident');
inc.addEncodedQuery(ecq);
inc.query();
while (inc.next()){
gs.print(inc.number);
}
// Result: Same as above, demonstrates storing query in a variable.
addActiveQuery() / addInactiveQuery(): Convenience for Status
These are handy shortcuts. addActiveQuery() is equivalent to addQuery('active', true), and addInactiveQuery() is addQuery('active', false).
// Exercise - 10: Using addActiveQuery
var inc = new GlideRecord('incident');
inc.addActiveQuery(); // Adds active=true
inc.addQuery('priority', '1');
inc.query();
while (inc.next()){
gs.info(inc.number + ' (Active P1)');
}
// Result: Prints active incidents with priority 1.
// Exercise - 10 (again): Using addInactiveQuery
var inc = new GlideRecord('incident');
inc.addInactiveQuery(); // Adds active=false
inc.addQuery('priority', '1');
inc.query();
while (inc.next()) {
gs.print(inc.number + ' (Inactive P1)');
}
// Result: Prints inactive incidents with priority 1 (e.g., closed incidents).
addNullQuery('field_name') / addNotNullQuery('field_name'): Checking for Empty Fields
These methods are perfect for finding records where a specific field is empty (null) or has a value (not null).
// Exercise - 32: Find incidents with an empty short description
var inc = new GlideRecord('incident');
inc.addNullQuery('short_description');
inc.query();
while (inc.next()) {
gs.print(inc.number + ' has a null short description.');
}
// Result: Prints incident numbers where the short_description field is empty.
// Exercise - 33: Find incidents with a populated short description
var inc = new GlideRecord('incident');
inc.addNotNullQuery('short_description');
inc.query();
while (inc.next()) {
gs.print(inc.number + ' has a short description.');
}
// Result: Prints incident numbers where the short_description field is not empty.
orderBy('field_name') / orderByDesc('field_name'): Sorting Your Results
Want your query results in a specific order? These methods add ORDER BY clauses to your query, arranging records in ascending or descending order based on a field.
// Exercise - 11: Order incidents by short description (Ascending)
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.orderBy('short_description'); // Sort by short_description in ascending order
inc.query();
while (inc.next()){
gs.print(inc.number + ' - ' + inc.short_description);
}
// Result: Prints P1 incidents, sorted alphabetically by short description.
// Exercise - 12: Order incidents by short description (Descending)
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.orderByDesc('short_description'); // Sort by short_description in descending order
inc.query();
while (inc.next()){
gs.print(inc.number + ' - ' + inc.short_description);
}
// Result: Prints P1 incidents, sorted reverse-alphabetically by short description.
setLimit(number): Controlling Result Size
Crucial for performance! setLimit() restricts the number of records returned by your query. Always use this when you only need a few records, especially in large tables, to avoid unnecessary database load.
// Exercise - 13: Get only the latest 10 P1 incidents
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.orderByDesc('sys_created_on'); // Get the latest ones
inc.setLimit(10); // Limit to 10 records
inc.query();
while (inc.next()){
gs.print(inc.number + ' - ' + inc.short_description);
}
// Result: Prints the 10 most recently created P1 incidents.
get(sys_id_or_field, value): Fetching a Single Record Directly
If you know the sys_id or a unique field (like number for incidents), get() is your fastest way to retrieve a single record. It implicitly queries and moves to the first record if found.
// Exercise - 14: Get record by incident number
var inc = new GlideRecord('incident');
inc.get('number', 'INC0009005'); // Fetch the record with this incident number
gs.print('The sys_id for INC0009005 is: ' + inc.sys_id);
// Result: Prints the sys_id corresponding to INC0009005.
// Or by sys_id
var inc2 = new GlideRecord('incident');
var incidentSysId = 'YOUR_INCIDENT_SYS_ID_HERE'; // Replace with an actual sys_id
if (inc2.get(incidentSysId)) { // If the record is found
gs.print('Incident number for ' + incidentSysId + ' is: ' + inc2.number);
} else {
gs.print('Record not found for sys_id: ' + incidentSysId);
}
// Result: Prints the incident number for the given sys_id (or "not found").
chooseWindow(first_row, last_row_excluded): Paging Through Results
This method allows you to fetch a specific "window" of records from your query result set, useful for pagination. The last_row_excluded means it will fetch up to, but not including, that row number.
// Exercise - 15: Display records from index 3 up to (but not including) 7
var inc = new GlideRecord('incident');
inc.addActiveQuery();
inc.orderBy('number'); // Ensure consistent order for windowing
inc.chooseWindow(3, 7); // Gets records at index 3, 4, 5, 6 (4 records)
inc.query();
while (inc.next()){
gs.print(inc.number);
}
// Result: Prints incident numbers corresponding to rows 3-6 of the active incidents (assuming enough exist).
Retrieving Record Information and Properties
getRowCount(): Counting Your Records
After a query(), this method tells you how many records were returned by your query. It's often used for displaying counts or conditional logic.
// Exercise - 16: Display total number of active users
var user = new GlideRecord('sys_user');
user.addActiveQuery();
user.query();
gs.print('Total active users: ' + user.getRowCount());
// Result: Prints the total count of active users in the sys_user table.
getValue('field_name'): Getting the Actual Value
Retrieves the raw, backend value of a specified field for the current record. This is what's actually stored in the database.
// Exercise - 18: Get the actual value of short_description
var inc = new GlideRecord('incident');
inc.addActiveQuery();
inc.setLimit(1); // Just get one active record for demonstration
inc.query();
if (inc.next()){
gs.print('Raw short description value: ' + inc.getValue('short_description'));
}
// Result: Prints the raw short description value (e.g., 'VPN Issue')
getDisplayValue('field_name'): Getting the User-Friendly Value
Retrieves the user-friendly display value of a field, especially useful for reference fields, choice lists, or translated text. For example, for a priority field, getValue() might return '1', while getDisplayValue() returns 'Critical'.
// Exercise - 19: Print the display value of priority
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.setLimit(1);
inc.query();
if (inc.next()){
gs.print('Priority actual value: ' + inc.priority + ' | Display value: ' + inc.priority.getDisplayValue());
// You can also use inc.getDisplayValue('priority')
}
// Result: Prints "Priority actual value: 1 | Display value: Critical"
getTableName() / getRecordClassName(): Identifying the Table
These methods return the name of the table the GlideRecord object is currently associated with. getRecordClassName() is effectively the same as getTableName() for most practical purposes when working with tables.
// Exercise - 17: Get the table name
var inc = new GlideRecord('change_request');
gs.print('The table name is: ' + inc.getTableName());
gs.info('The record class name is: ' + inc.getRecordClassName());
// Result: "The table name is: change_request" and "The record class name is: change_request" in logs.
hasNext(): Checking for More Records
Returns true if there are more records in the result set to iterate through with next(), otherwise false. Often used in loops or conditional statements before calling next().
// Exercise - 19: Check if there are any records
var inc = new GlideRecord('incident');
inc.query();
gs.print('Does the query have any results? ' + inc.hasNext());
// Result: Prints 'true' if any incidents exist, 'false' otherwise.
getUniqueValue(): Getting the Record's Identifier
This method returns the unique identifier of the current record, which is almost always the sys_id.
// Exercise - 20: Get the sys_id of the current record
var inc = new GlideRecord('incident');
inc.query();
if (inc.next()){
var uniqueValue = inc.getUniqueValue();
gs.print('Unique value (sys_id) of the first incident: ' + uniqueValue);
}
// Result: Prints the sys_id of the first incident record found.
getElement('field_name'): Accessing a GlideElement Object
Returns a GlideElement object for the specified field. This allows you to access more properties and methods specific to that field, such as getLabel(), isMandatory(), getChoices(), etc.
// Exercise - 23: Get the GlideElement for short_description
var inc = new GlideRecord('incident');
inc.initialize(); // Initialize a new record
inc.setValue('short_description','I am facing VPN Problem');
inc.insert();
var shortDescElement = inc.getElement('short_description');
gs.print('Value via getElement: ' + shortDescElement.getValue());
gs.print('Label of short_description: ' + shortDescElement.getLabel());
// Result: Prints the value and label of the short_description field.
getLink(false) and gs.getProperty('glide.servlet.uri'): Generating Record Links
Useful for constructing direct links to records in your instance, for notifications or external references. getLink(false) provides a relative URL, which you combine with the instance base URL.
// Exercise - 29: Get the link to a record
var inc = new GlideRecord('incident');
inc.addActiveQuery();
inc.setLimit(1);
inc.query();
if (inc.next()){
var instanceUrl = gs.getProperty('glide.servlet.uri'); // Base URL of your instance
var recordLink = inc.getLink(false); // Link specific to the record (false for relative URL)
gs.print('Link to incident ' + inc.number + ': ' + instanceUrl + recordLink);
}
// Result: Prints a full URL like "https://yourinstance.service-now.com/nav_to.do?uri=incident.do?sys_id=..."
CRUD Operations: Creating, Reading, Updating, Deleting
initialize(): Preparing a New Record
This method prepares a new, empty record in memory, setting default values for fields. It's the first step before you can set field values and insert() a new record. Think of it as opening a blank form.
newRecord(): Another Way to Prepare a New Record
Similar to initialize(), this creates a new GlideRecord record and assigns a unique ID. It's often used interchangeably with initialize() for creating new records.
// Exercise - 31: Create a new record using newRecord()
var inc = new GlideRecord('incident');
inc.newRecord(); // Creates a new record in memory
inc.short_description = 'Creating new record via script';
inc.category = 'software';
inc.insert(); // Saves the new record to the database
gs.print('New incident created: ' + inc.number);
// Result: Creates a new incident and prints its number.
setValue('field_name', 'value'): Setting Field Values
This method sets the value for a specific field on the current record. It's the programmatic way to fill out fields before insert() or update().
// Exercise - 22: Create a new record and set values
var inc = new GlideRecord('incident');
inc.initialize();
inc.setValue('category', 'network');
inc.setValue('short_description', 'Critical VPN Issue Detected');
inc.insert();
gs.print('Category is ' + inc.category.getDisplayValue() + ' and issue is: ' + inc.short_description);
// Result: Creates a new incident with specified category and short description.
insert(): Creating a Single New Record
After initialize() or newRecord() and setting field values, insert() saves the new record to the database. This is your "save" button for new entries.
// Exercise - 25: Initialize and insert an incident
var inc = new GlideRecord('incident');
inc.initialize(); // Compose incident form
inc.category = 'network'; // Directly set field values
inc.short_description = 'Firewall Issue Blocking Access';
inc.priority = 1;
inc.insert(); // Create new record in the database
gs.print('New incident created with number: ' + inc.number);
// Result: Creates a new incident and prints its assigned number.
update(): Modifying a Single Record
Used to save changes to an existing record. You must first query or get() the record, make your changes, and then call update().
// Exercise - 34: Update a specific incident record
var inc = new GlideRecord('incident');
inc.get('number', 'INC0000057'); // Fetch the record to update
if (inc.isValidRecord()) { // Always check if the record exists before updating
inc.setValue('state', 2); // Set state to 'In Progress'
inc.update(); // Save the changes
gs.print('Incident ' + inc.number + ' state updated to: ' + inc.state.getDisplayValue());
} else {
gs.print('Incident INC0000057 not found.');
}
// Result: Updates the state of INC0000057 and prints confirmation.
updateMultiple(): Modifying Multiple Records Efficiently
This powerful method allows you to update all records returned by your query with a specified set of changes in a single database operation. It's far more efficient than looping through records and calling update() for each one, especially for large datasets.
// Exercise - 35: Update multiple incidents
var inc = new GlideRecord('incident');
inc.addQuery('category', 'hardware'); // Find all hardware incidents
inc.setValue('category', 'software'); // Change their category to software
inc.updateMultiple(); // Apply the change to all found records
gs.print('All hardware incidents categories updated to software.');
// Result: All incidents previously categorized as 'hardware' will now be 'software'.
deleteRecord(): Deleting a Single Record
Deletes the current record from the database. Make sure you've correctly identified the record using get() or `query()` and `next()` before calling this.
// Exercise - 36: Delete a single incident record (USE WITH EXTREME CAUTION!)
var inc = new GlideRecord('incident');
inc.get('number', 'INC0010013'); // Identify the record to delete
if (inc.isValidRecord()) {
inc.deleteRecord(); // Delete it!
gs.print('Incident INC0010013 has been deleted.');
} else {
gs.print('Incident INC0010013 not found for deletion.');
}
// Result: Deletes INC0010013 from the database. Verify before running!
deleteMultiple(): Deleting Multiple Records (ULTRA CAUTION!)
Deletes all records that satisfy your query condition in a single operation. This is incredibly dangerous if your query is too broad. Always test thoroughly in non-prod and back up data if possible before running in prod. This method has no "undo" button!
// Exercise - 37: Delete multiple incidents (USE WITH CATASTROPHIC CAUTION!)
var inc = new GlideRecord('incident');
inc.addQuery('priority', 4); // Find all incidents with priority 4 (Low)
inc.deleteMultiple(); // Delete all of them
gs.print('All incidents with priority 4 have been deleted.');
// Result: Deletes all P4 incidents. Ensure this is your intention!
Validation and Permissions
isNewRecord(): Checking Record Status
Returns true if the current GlideRecord object represents a new record that has not yet been inserted into the database (i.e., it was created with initialize() or newRecord() but not insert()ed yet).
// Exercise - 26: Check if a record is new
var inc = new GlideRecord('incident');
inc.newRecord(); // Create a new record in memory
gs.info('Is this a new record before insert? ' + inc.isNewRecord()); // True
inc.short_description = 'Test new record';
inc.insert();
gs.info('Is this a new record after insert? ' + inc.isNewRecord()); // False
// Result: First true, then false after insertion.
isValid(): Checking Table Existence
Determines if the table specified when instantiating the GlideRecord object actually exists in the ServiceNow schema.
// Exercise - 27: Check if 'incident' table is valid
var inc = new GlideRecord('incident');
gs.print('Is "incident" a valid table? ' + inc.isValid()); // Result: True
// Example 2: Check for a non-existent table
var nonExistentGr = new GlideRecord('uday_custom_table'); // Assuming 'uday_custom_table' doesn't exist
gs.print('Is "uday_custom_table" a valid table? ' + nonExistentGr.isValid()); // Result: False
isValidField('field_name'): Checking Field Existence
Checks if a specified field exists within the table associated with the GlideRecord object.
// Exercise - 28: Check if 'category' is a valid field on incident
var inc = new GlideRecord('incident');
gs.print('Is "category" a valid field on incident? ' + inc.isValidField('category')); // Result: True
gs.print('Is "non_existent_field" a valid field on incident? ' + inc.isValidField('non_existent_field')); // Result: False
isValidRecord(): Checking if a Record was Found
After a get() or query() and next(), this method tells you if a record was successfully retrieved. Crucial for preventing errors when trying to access properties of a non-existent record.
// Exercise - 30: Check if a specific record exists
var inc = new GlideRecord('incident');
inc.get('number', 'INC0010012');
gs.print(inc.number + ' exists: ' + inc.isValidRecord()); // Result: True (if INC0010012 exists)
var inc2 = new GlideRecord('incident');
inc2.get('number', 'INC9999999'); // Assuming this doesn't exist
gs.print(inc2.number + ' exists: ' + inc2.isValidRecord()); // Result: False
canCreate(), canRead(), canWrite(), canDelete(): ACL Enforcement
These methods check if the currently logged-in user (or the system user if running in background script) has the necessary Access Control List (ACL) permissions to perform the respective operation on the GlideRecord's table and current record. This is vital for security and preventing unauthorized data manipulation.
// Exercise - 38 to 41: Check permissions
var inc = new GlideRecord('incident');
gs.print('Can create incidents? ' + inc.canCreate());
gs.print('Can read incidents? ' + inc.canRead());
gs.print('Can write to incidents? ' + inc.canWrite());
gs.print('Can delete incidents? ' + inc.canDelete());
// Results: Will return true/false based on the user's roles and ACLs.
Advanced and Utility Methods
autoSysFields(false) and setWorkflow(false): Bypassing System Automation
These methods are used when you want to update records without triggering standard system behaviors:
autoSysFields(false): Prevents the automatic update of system fields likesys_updated_by,sys_updated_on,sys_mod_countwhen a record is updated via script. Useful for data migration or historical data imports. (Note: May not work in scoped applications).setWorkflow(false): Prevents business rules, workflows, and other automation from running when a record is updated. Use with extreme care, as it can bypass critical business logic and lead to data inconsistencies.
// Exercise - 43: Update records without triggering system fields/workflow
var inc = new GlideRecord('incident');
inc.addQuery('state', 1); // Find all new incidents
inc.query();
while (inc.next()) {
inc.autoSysFields(false); // Do not update sys_updated_by, sys_updated_on, etc.
inc.setWorkflow(false); // Do not run business rules/workflows
inc.setValue('state', 2); // Change state to 'In Progress'
inc.update();
}
gs.print('Incidents updated without affecting system fields or workflows.');
// Result: Updates specified incidents, but without recording script-triggered updates in system fields or executing associated automation.
addJoinQuery('joined_table', 'primary_field', 'foreign_field'): Connecting Tables
Allows you to query records based on a relationship with another table. This is equivalent to an SQL JOIN operation, linking records between two tables based on matching field values.
// Exercise - 44: Find problems that have an incident attached
var prob = new GlideRecord('problem');
// Join problem with incident where problem.opened_by matches incident.caller_id
prob.addJoinQuery('incident', 'opened_by', 'caller_id');
prob.query();
while(prob.next()){
gs.print('Problem ' + prob.number + ' is linked to incidents by caller.');
}
// Result: Displays numbers of problem records that have associated incidents where the problem's opened_by matches the incident's caller_id.
setAbortAction(true): Stopping an Action
Typically used in Business Rules (especially before Business Rules) to prevent a database operation (insert, update, delete) from completing if a certain condition isn't met. It rolls back the current transaction, preventing the record from being saved.
// Exercise - 45: Example in a Business Rule (context: 'current' record is available)
// Assume this is a 'before insert/update' Business Rule on a table with u_date1 and u_date2 fields.
/*
if ((!current.u_date1.nil()) && (!current.u_date2.nil())) {
var start = current.u_date1.getGlideObject().getNumericValue(); // Get date as numeric value
var end = current.u_date2.getGlideObject().getNumericValue();
if (start > end) {
gs.addInfoMessage('Start date must be before End date');
current.u_date1.setError('Start date must be before End date'); // Highlight field error
current.setAbortAction(true); // Stop the record from being saved
}
}
*/
// Result: If start date is after end date, an info message appears, the field is highlighted, and the record save is prevented.
Best Practices & Troubleshooting Your GlideRecord Scripts
Mastering GlideRecord isn't just about knowing the methods; it's about using them wisely, efficiently, and securely.
Performance Optimization is Key!
- Always Filter: Never run
query()withoutaddQuery()oraddEncodedQuery()unless you explicitly intend to fetch all records (rarely the case in production for large tables). Unfiltered queries can bring your instance to a crawl. - Use
setLimit(): When you only need a few records, usesetLimit()to fetch only what's necessary. This significantly reduces database load and speeds up your script. - Prefer
get()for Single Records: If you know thesys_idor a unique key,gr.get(sys_id)is usually the most performant way to fetch a single record. updateMultiple()/deleteMultiple(): For bulk operations, these are vastly more efficient than looping through records and callingupdate()ordeleteRecord()repeatedly. They perform the operation as a single database transaction.- Avoid Loops within Loops: Nested GlideRecord queries can be performance nightmares. Look for ways to use
addJoinQuery()or pre-fetch data into arrays/objects if possible to minimize database calls.
Security and Data Integrity
- Validate Input: Always validate any user input or data from external sources before using it in GlideRecord operations. Malicious input could lead to unintended consequences.
- ACLs are Your Friend: Use
canCreate(),canRead(),canWrite(),canDelete()to respect security rules, especially when impersonating users or performing operations on behalf of others. This ensures your script adheres to the platform's security model. - Cautious with
setWorkflow(false)andautoSysFields(false): These bypass critical system behaviors (business rules, email notifications, audit trails). Only use them when you fully understand the implications and have a strong, documented reason. - Test in Non-Production: We can't stress this enough. Especially
deleteMultiple(),updateMultiple(), or any script that modifies data extensively. Always test in your Personal Developer Instance (PDI) or a dedicated sub-production environment first.
Troubleshooting Tips
gs.print()/gs.info(): Your primary tools for seeing what your script is doing. Use them liberally to print variable values, query conditions, and record counts throughout your script.- Encoded Query Check: If your
addQuery()oraddEncodedQuery()isn't working as expected, build the query in a list view, copy the encoded query, and compare it to what your script is generating (you can printgr.getEncodedQuery()to see what GlideRecord thinks it's doing). isValidRecord()/isValidField(): Use these to verify that a record was found or a field exists before trying to access its properties, preventing runtime errors (e.g., trying to accessgr.numberwhengr.get()failed).- ServiceNow Script Debugger: For complex scripts, the server-side script debugger (available in paid editions or PDI) is invaluable for stepping through code, setting breakpoints, and inspecting variables in real-time.
GlideRecord in the Real World: Your Interview Advantage
GlideRecord isn't just academic; it's the language of server-side customization. You'll use it every single day in various capacities:
- Business Rules: To automate actions before/after record operations (e.g., automatically assign an incident to a group based on category).
- Script Includes: To build reusable functions for data access, making your code modular and maintainable.
- UI Actions: To perform server-side logic from a button click (e.g., creating a related record, transitioning a workflow state).
- Workflows & Flow Designer: In script steps to manipulate records and make decisions based on data.
- Integration Hub: To process incoming/outgoing data, often transforming it between systems.
- Fix Scripts: For one-time data cleanup, migration tasks, or setting up initial data in a new instance.
Interview Relevance: Expect GlideRecord questions in almost any ServiceNow developer interview. Hiring managers want to see if you understand the core concepts and can apply them safely and efficiently. Be prepared to explain:
- The fundamental purpose of GlideRecord.
- The difference between
addQuery()andaddEncodedQuery(), and when to use each. - How to create, read, update, and delete records programmatically.
- When to use
get()vs.query(). - The importance of
setLimit()and other performance considerations. - The purpose of
setWorkflow(false)andautoSysFields(false), and their associated risks. - How to ensure your GlideRecord scripts are performant and secure.
- How to debug GlideRecord scripts.
Conclusion: Embrace the Power of GlideRecord
Phew! That was a deep dive, wasn't it? If you've made it this far, congratulations! You now have a solid understanding of GlideRecord architecture and its essential methods. GlideRecord is more than just an API; it's the fundamental tool that empowers you to interact with the ServiceNow database, create dynamic functionalities, and solve complex business challenges through server-side scripting.
Keep practicing with these methods in your Personal Developer Instance (PDI). Experiment, break things (in your PDI!), and then fix them. The more you use GlideRecord, the more intuitive it becomes. Soon, you'll be manipulating data and building robust solutions in ServiceNow with confidence and flair. Happy scripting!