What It Is
getValue() is the GlideRecord method that retrieves a field's raw database value as a string, solving the fundamental problem of data integrity when working with ServiceNow's dual-value field system. While ServiceNow stores internal values in the database (sys_ids for references, actual boolean values, raw dates), the platform also maintains display representations for human consumption. This method cuts through ServiceNow's display layer to access the actual stored data, which is critical for reliable scripting, integrations, and business logic that cannot afford to be corrupted by localization, user preferences, or display formatting.
Architecturally, getValue() operates exclusively on the server side within the Rhino JavaScript engine, where GlideRecord objects live and database connections exist. It cannot and does not execute in browsers—any client-side script that appears to use getValue() is actually triggering a server round-trip through GlideAjax, Script Includes, or similar mechanisms. This server-only execution gives it direct access to the database layer and ServiceNow's internal field processing, but also means every call carries the overhead of server-side execution context.
Under the hood, ServiceNow implements getValue() as a direct interface to the GlideElement class hierarchy, where each field type has specific logic for converting its internal representation to a string. Reference fields return their 32-character sys_id, date/time fields return ISO-formatted strings in the system timezone, choice fields return their internal choice values (not labels), and encrypted fields may return obfuscated values depending on security context. The method bypasses all display business rules, UI policies, and user-specific formatting preferences that would normally influence what you see on forms.
Without getValue(), you cannot reliably compare reference fields, build accurate integrations, or ensure data consistency across different user sessions and locales. Direct field access (gr.assigned_to) returns GlideElement objects that automatically convert to display values when used in string contexts, leading to fragile code that breaks when users have different language preferences or when display business rules change. Any script that queries reference fields, builds REST API responses, or performs field comparisons needs the internal value to function correctly across all environments and user contexts.
Developers use getValue() constantly in business rules, script includes, and background scripts for data manipulation and integration work. Architects rely on it when designing API contracts and data synchronization patterns that must remain stable regardless of instance configuration changes. System administrators need it less frequently, but encounter it when debugging workflows, building reports with accurate reference field data, or troubleshooting integration issues where display values have corrupted business logic.
getValue() forms a critical triad with getDisplayValue() and setValue() in ServiceNow's field manipulation arsenal. While getDisplayValue() retrieves human-readable representations and setValue() accepts either internal or display values for field updates, getValue() provides the foundation for data-driven logic that must remain consistent. It also relates closely to GlideElement's implicit string conversion behavior, though accessing that through direct field references creates the display value problems that explicit getValue() calls avoid.
How It Works Under the Hood
When you call getValue() on a GlideRecord field, ServiceNow's Rhino JavaScript engine delegates the call to the appropriate GlideElement subclass based on the field's data type. The platform maintains a registry of field type handlers—GlideElementReference for reference fields, GlideElementBoolean for boolean fields, GlideElementGlideDateTime for datetime fields—each with specialized logic for converting internal database values to their string representations. This type-specific processing ensures that complex field types like encrypted fields, currencies, and journals return meaningful string values rather than raw binary data or internal identifiers.
The method execution bypasses ServiceNow's display layer entirely, including any display business rules, UI policies, client scripts, or formatting rules that would normally modify field presentation. This direct database-to-string conversion happens within the same transaction context as your script, meaning the returned value reflects the current state of the record without requiring additional database queries. For reference fields specifically, ServiceNow retrieves the sys_id from the foreign key column without performing a join to fetch display values from the referenced table, making getValue() operations significantly faster than their getDisplayValue() counterparts.
Critically, the string conversion process respects the system's timezone and locale settings at the server level, not individual user preferences. Date and datetime fields return values in the instance's default timezone with ISO 8601 formatting, while numeric fields use period decimal separators regardless of user locale. This server-side standardization makes getValue() output predictable and suitable for programmatic consumption, but means the returned values may not match what users see in forms if their personal settings differ from system defaults.
The Request Lifecycle
- JavaScript engine receives the
getValue('field_name')method call on a GlideRecord instance - GlideRecord validates the field name exists in the table schema and retrieves the field's data dictionary definition
- Platform instantiates the appropriate GlideElement subclass based on field type (reference, choice, datetime, etc.)
- GlideElement retrieves the raw database value from the current record's field buffer in memory
- Type-specific conversion logic processes the internal value (sys_id extraction for references, timezone conversion for dates)
- Method returns the processed value as a JavaScript string to the calling script context
Enjoying this? Get one deep-dive per week.
Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.
The Core Pattern
function onChange(control, oldValue, newValue, isLoading) {
// Client-side scripts cannot call getValue() directly
// Must use server round-trip for internal values
if (isLoading || newValue === '') return;
var ga = new GlideAjax('IncidentUtils');
ga.addParam('sysparm_name', 'getAssignmentGroupManager');
ga.addParam('sysparm_group_id', newValue); // This is the sys_id from client
ga.getXMLAnswer(function(response) {
var manager_id = response; // Server returns internal sys_id value
// Set the manager field using internal value for data integrity
if (manager_id) {
g_form.setValue('u_group_manager', manager_id);
}
});
}var IncidentUtils = Class.create();
IncidentUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
getAssignmentGroupManager: function() {
var groupId = this.getParameter('sysparm_group_id');
if (!groupId) return '';
var groupGR = new GlideRecord('sys_user_group');
if (!groupGR.get(groupId)) return '';
// getValue() returns the sys_id, not display name
// Critical for setting reference fields correctly
var managerId = groupGR.getValue('manager');
// Always return internal values from server-side Ajax calls
// Client expects sys_id, not "John Smith"
return managerId || '';
},
type: 'IncidentUtils'
});Real-World Scenarios
Assignment Group Escalation Logic
A business rule needs to escalate incidents to the next level assignment group when SLA thresholds are breached. The rule must compare current assignment group against a predefined escalation hierarchy without being affected by group name changes or user display preferences.
// Get current assignment group sys_id for reliable comparison
var currentGroupId = current.getValue('assignment_group');
if (!currentGroupId) return; // Cannot escalate without current group
// Build escalation mapping using sys_ids, never display names
var escalationMap = {
'a1b2c3d4e5f6789012345678901234567': '9876543210fedcba098765432109876543', // L1 to L2
'9876543210fedcba098765432109876543': 'fedcba0987654321fedcba0987654321ab', // L2 to L3
};
// Look up next level group ID from our reliable mapping
var nextGroupId = escalationMap[currentGroupId];
if (nextGroupId) {
// Set using sys_id to ensure assignment succeeds regardless of group renames
current.setValue('assignment_group', nextGroupId);
current.setValue('assigned_to', ''); // Clear individual assignment
gs.addInfoMessage('Incident escalated to next level support group');
}var EscalationUtils = Class.create();
EscalationUtils.prototype = {
getEscalationChain: function(currentGroupId) {
var chain = [];
var groupGR = new GlideRecord('sys_user_group');
// Build complete escalation path using sys_ids
groupGR.addQuery('sys_id', currentGroupId);
groupGR.query();
while (groupGR.next()) {
chain.push({
id: groupGR.getValue('sys_id'), // Internal value for updates
name: groupGR.getDisplayValue('name'), // Display for logging
parent: groupGR.getValue('parent') // sys_id for next lookup
});
var parentId = groupGR.getValue('parent');
if (parentId) groupGR.get(parentId);
else break;
}
return chain;
}
};Using getValue() for reference field comparisons ensures the logic works even when assignment group names change or when the business rule runs in different user contexts. Direct field access would fail if groups get renamed, breaking critical escalation workflows. Watch for edge cases where assignment groups are deleted—the sys_id comparison will fail gracefully while display value logic might throw exceptions.
REST API Data Export
A scripted REST API endpoint exports request item data to an external system that requires stable reference field identifiers. The external system stores ServiceNow sys_ids and cannot handle display value changes when user names or catalog item names are updated.
// Export request items with stable reference field values
var requestItems = [];
var ritm = new GlideRecord('sc_req_item');
ritm.addQuery('request', request.pathParams.request_id);
ritm.addQuery('active', true);
ritm.query();
while (ritm.next()) {
var item = {
sys_id: ritm.getValue('sys_id'),
number: ritm.getValue('number'),
// Use getValue() for reference fields to maintain external system links
requested_for: ritm.getValue('request.requested_for'),
cat_item: ritm.getValue('cat_item'),
assignment_group: ritm.getValue('assignment_group'),
assigned_to: ritm.getValue('assigned_to'),
// Include display values separately for human readability
requested_for_name: ritm.getDisplayValue('request.requested_for'),
state: ritm.getValue('state') // Choice fields return internal values
};
requestItems.push(item);
}// Transform incoming external data using sys_id references
var ExternalSyncUtils = Class.create();
ExternalSyncUtils.prototype = {
syncRequestItem: function(externalData) {
var ritm = new GlideRecord('sc_req_item');
if (ritm.get(externalData.servicenow_id)) {
// Update assignment using sys_id from external system
if (externalData.assigned_user_id) {
// Validate the sys_id exists before setting
var userGR = new GlideRecord('sys_user');
if (userGR.get(externalData.assigned_user_id)) {
ritm.setValue('assigned_to', externalData.assigned_user_id);
}
}
// getValue() ensures we log the actual database value being updated
gs.info('Updated RITM {0} assigned_to from {1} to {2}',
ritm.getValue('number'),
ritm.getValue('assigned_to'),
externalData.assigned_user_id);
}
}
};REST APIs must use getValue() for reference fields to maintain referential integrity with external systems. Display values create fragile integrations that break when referenced records are updated. Be cautious with dot-walking (request.requested_for) in getValue() calls—if intermediate references are empty, the call returns empty strings rather than throwing errors.
Multi-Instance Data Migration
A background script migrates configuration item data between development and production instances where user names and reference field display values differ between environments. The script must preserve logical relationships using sys_ids while handling missing references gracefully.
// Export CIs with all reference relationships preserved
var exportData = [];
var ci = new GlideRecord('cmdb_ci_server');
ci.addQuery('install_status', 1); // Only installed CIs
ci.query();
while (ci.next()) {
var record = {
name: ci.getValue('name'),
// getValue() captures exact reference field values for recreation
owned_by: ci.getValue('owned_by'),
managed_by: ci.getValue('managed_by'),
support_group: ci.getValue('support_group'),
location: ci.getValue('location'),
// Include display values for manual verification if needed
owned_by_display: ci.getDisplayValue('owned_by'),
location_display: ci.getDisplayValue('location'),
// Choice and string fields work reliably with getValue()
operational_status: ci.getValue('operational_status'),
environment: ci.getValue('environment')
};
exportData.push(record);
gs.print('Exported CI: ' + record.name + ' (' + ci.getValue('sys_id') + ')');
}// Import script validates references before creating records
var CIMigrationUtils = Class.create();
CIMigrationUtils.prototype = {
validateAndImport: function(importRecord) {
var ci = new GlideRecord('cmdb_ci_server');
ci.initialize();
// Set simple fields using getValue() equivalents
ci.setValue('name', importRecord.name);
ci.setValue('operational_status', importRecord.operational_status);
// Validate reference field sys_ids exist in target instance
if (importRecord.owned_by) {
var user = new GlideRecord('sys_user');
if (user.get(importRecord.owned_by)) {
ci.setValue('owned_by', importRecord.owned_by);
} else {
gs.warn('User not found: {0} ({1})',
importRecord.owned_by_display, importRecord.owned_by);
}
}
var newId = ci.insert();
return newId;
}
};Data migration scripts depend on getValue() to capture portable reference relationships that work across instances with different user populations and organizational structures. Display value-based migrations fail catastrophically when target instances have differently named users or groups. Always validate that reference sys_ids exist in the target instance before attempting setValue() operations—invalid sys_ids will be silently ignored, leaving reference fields empty.
The Classic Mistake
Using getValue() on Reference fields to compare display names instead of sys_ids, causing lookup failures.
// Business Rule on incident table
(function executeRule(current, previous) {
// Developer thinks this compares user names
var currentAssignee = current.assigned_to.getValue();
var previousAssignee = previous.assigned_to.getValue();
// This breaks - comparing sys_ids to display names
if (currentAssignee != 'John Smith' && previousAssignee == 'John Smith') {
// Send notification when John Smith is unassigned
gs.addInfoMessage('Assignment changed from John Smith');
// This query will never find records
var gr = new GlideRecord('sys_user');
gr.addQuery('name', currentAssignee); // Querying name field with sys_id value
gr.query();
if (gr.next()) {
gs.log('New assignee: ' + gr.name);
}
}
})(current, previous);This fails because getValue() on Reference fields returns the sys_id (like '62826bf03710200044e0bfc8bcbe5df1'), never the display name. The browser console shows no errors, but the condition never evaluates true and the query returns zero results. ServiceNow internally stores only sys_ids in Reference fields - display names are resolved at render time through table joins. When you compare a sys_id against 'John Smith', JavaScript performs a string comparison that always fails silently.
// Business Rule on incident table
(function executeRule(current, previous) {
// Use getDisplayValue() for human-readable comparisons
var currentAssignee = current.assigned_to.getDisplayValue();
var previousAssignee = previous.assigned_to.getDisplayValue();
if (currentAssignee != 'John Smith' && previousAssignee == 'John Smith') {
gs.addInfoMessage('Assignment changed from John Smith');
// Or use getValue() to get sys_id for queries
var assigneeSysId = current.assigned_to.getValue();
if (assigneeSysId) {
var gr = new GlideRecord('sys_user');
gr.get(assigneeSysId); // Direct sys_id lookup - much faster
gs.log('New assignee: ' + gr.name);
}
}
})(current, previous);getValue() for sys_ids and database operations, getDisplayValue() for human comparisons and UI display.
Performance Rules
- Never call
getValue()inside loops over more than 100 records - each call triggers field access overhead that compounds to 5-10 second page load times and browser timeouts. - Use direct field access (
gr.field_name) instead ofgetValue('field_name')when the field name is known at development time - eliminates string lookup overhead that adds 200-300ms per 1000 field reads. - Cache
getValue()results in variables when the same field is accessed more than 3 times in a single script - repeated calls on Reference fields trigger redundant database lookups causing 30+ second Business Rule execution times. - Avoid
getValue()on Journal fields in client scripts - these fields can contain 50KB+ of data that causes browser memory spikes and 'script unresponsive' dialogs when loaded on form initialization. - Replace
getValue()withgetUniqueValue()when building REST API responses that include sys_ids -getValue()forces string conversion overhead that adds 2-3 seconds to responses containing 500+ records. - Never call
getValue()on encrypted fields in scheduled jobs - decryption operations are CPU-intensive and cause job queue backups when processing more than 50 records with encrypted data per execution. - Implement null checks before
getValue()calls on optional Reference fields - callinggetValue()on empty References generates database queries for non-existent records, adding 100-200ms overhead per empty field in production.
Side Effects & Platform Behavior
- Field access through
getValue()triggers ACL evaluation on the target field, writing Access entries tosys_security_logwhen Security Debug is enabled. - Reading Reference fields via
getValue()updates thesys_glide_object_logtable with field access statistics used by Performance Analytics for database optimization reports. - Client-side
getValue()calls on dirty fields force immediate AJAX synchronization with the server, visible in browser Network tab as POST requests to/api/now/ui/formendpoints. - Accessing encrypted fields through
getValue()creates audit records insys_security_logwith operation type 'field_access' and includes the accessing user's session ID for compliance tracking. - Using
getValue()in Before Business Rules on Reference fields can trigger recursive rule execution if the referenced record has Display Rules that modify the current record, causing stack overflow errors. - Field reads via
getValue()increment theread_countmetric insys_db_cache_stats, affecting database cache optimization algorithms and potentially causing cache evictions for frequently-accessed fields. - Calling
getValue()on Journal fields triggers Journal Entry Business Rules on thesys_journal_entrytable, even for read operations, due to internal journal aggregation processes. - In scoped applications,
getValue()calls on cross-scope Reference fields write scope boundary violations tosys_scope_privilege_logwhen the target table lacks proper cross-scope access configuration. - Server-side
getValue()operations bypass client-side field validation rules but still trigger server-side Data Policies, creating inconsistent validation behavior between form submission and programmatic access. - Reading fields with active Transform Maps via
getValue()during import operations can trigger transformation scripts prematurely, writing 'premature_transform' warnings tosys_import_log.
Debugging When It Breaks
The most common failure symptoms include silent null returns when you expect values, sys_ids appearing in UI fields where you expected display names, and 'Cannot read property of undefined' errors in client scripts. Users typically report seeing technical values (like sys_ids or internal codes) instead of meaningful text, or form fields that appear empty despite having data. In Business Rules, you'll see logic branches that never execute because Reference field comparisons fail silently.
Check the browser's JavaScript console (F12 → Console) for client-side issues - look for 'TypeError: Cannot read property' messages when accessing nested Reference fields. For server-side problems, navigate to System Log → All and filter by your script name or source table. The Script Debugger (System Definition → Script Debugger) shows real-time getValue() return values and helps identify when fields return unexpected data types. Enable SQL logging under System Diagnostics → SQL to see if Reference field access triggers excessive database queries.
Watch for specific error patterns: 'NullPointerException' in server logs indicates calling getValue() on uninitialized GlideRecord objects. 'Invalid field name' warnings appear when field names contain typos or the field doesn't exist on the table. Client-side 'Synchronous XMLHttpRequest' warnings indicate getValue() calls forcing synchronous server communication. Performance logs show 'Long-running script' entries when field access becomes inefficient in loops.
Quick diagnostic checklist:
- Verify the GlideRecord has
next()orget()was called successfully beforegetValue() - Check field spelling in Dictionary (System Definition → Tables & Columns) - case sensitivity matters
- Test if the field contains data using
gr.field_name.nil()before accessing values - Confirm field-level Read ACLs allow access for the current user's role
- For Reference fields, verify the referenced record exists and isn't deleted
- Use
typeof gr.field_name.getValue()to check if return value matches expected data type
Quick Reference
- Reference field
getValue()always returns 32-character sys_id strings, never display names or objects - DateTime fields return ISO format strings ('2024-01-15 14:30:00') regardless of user's timezone or display preferences
- Boolean fields return string 'true' or 'false', not JavaScript boolean primitives - use
== 'true'for comparisons - Empty fields return empty string (''), never null or undefined - use
gr.field.nil()for proper null checking - Choice fields return internal values ('1', '2', '3') not choice labels - use
getDisplayValue()for user-friendly labels - Client-side
getValue()works only on loaded form fields - returns null for fields not visible on current form view - Journal fields via
getValue()return complete journal history as concatenated string, potentially 50KB+ of data - Currency fields return unformatted decimal strings ('1234.56') without currency symbols or thousand separators
- Encrypted fields require 'read_encrypter' role for
getValue()to return decrypted values - otherwise returns '***' placeholder - Use direct field access (
gr.number) instead ofgetValue('number')for 10-15% better performance in loops