What It Is
GlideUser is ServiceNow's session-aware user object that represents the currently authenticated user within any server-side execution context. It's the platform's primary mechanism for implementing role-based access control, user attribute checking, and session-specific business logic. Every server-side script—whether in Business Rules, Script Includes, or UI Actions—operates within the security context of a specific user, and GlideUser is how you interrogate and leverage that context. Without it, you'd have no way to determine who's performing an action, what they're authorized to do, or how to customize behavior based on user attributes.
Architecturally, GlideUser exists exclusively in server-side execution contexts—you'll never encounter it in Client Scripts or UI Policies. It's instantiated per user session and persists throughout the duration of that session's server-side operations. The object maintains a direct connection to the sys_user record but goes beyond simple database queries by caching role memberships, group associations, and frequently-accessed user attributes. This caching is crucial for performance since role checking happens constantly throughout request processing.
Under the hood, ServiceNow populates the GlideUser object during the authentication phase of each request, before any of your custom code executes. The platform queries the user's role assignments, resolves inherited roles, builds group membership lists, and loads user preferences into memory. This preprocessing means that methods like hasRole() and isMemberOf() don't hit the database repeatedly—they're checking cached data structures. The session ID, user sys_id, and security context are all bound together, making GlideUser both a convenience object and a security boundary.
You cannot implement meaningful security, audit trails, or user-specific customization without GlideUser. Every approval workflow that checks if the current user can approve, every assignment rule that routes tickets based on the submitter's location, every data policy that restricts field visibility—they all depend on GlideUser methods. Attempting to work around it by querying sys_user directly not only kills performance but also bypasses the platform's role inheritance and security model. I've seen developers try to replicate role checking with manual queries, and it never handles edge cases correctly.
Platform architects use GlideUser for designing security models and access control patterns. Developers rely on it for implementing business rules that vary by user role or department. Even administrators encounter it when configuring UI Actions that should only appear for specific groups. The pattern typically involves checking roles or group membership first, then branching logic based on those checks. Whether you're building a custom approval process, implementing field-level security, or creating role-specific g_form behavior, GlideUser is central to the implementation.
GlideUser works closely with GlideSession (the broader session context), GlideRecord (for querying user-related tables), and the Access Control framework. While GlideSession handles session variables and preferences, GlideUser focuses specifically on identity and authorization. It differs from GlideRecord queries against sys_user because it represents the living session rather than just database fields. The Access Control framework consumes GlideUser data to make table and field-level security decisions, making it a foundational component that other security mechanisms depend on rather than compete with.
How It Works Under the Hood
When a user authenticates to ServiceNow, the platform immediately constructs their security context before executing any custom code. This process involves querying sys_user for basic user attributes, then traversing sys_user_has_role and sys_user_grmember to build complete role and group membership lists. The platform also resolves role inheritance—if you have itil role, you automatically inherit all roles that itil contains. This security context gets cached in the user's session and attached to every server-side execution thread.
The GlideUser object is essentially a wrapper around this cached security context, providing methods that interrogate the pre-built data structures rather than hitting the database repeatedly. When you call gs.getUser().hasRole('incident_manager'), it's not running a query—it's checking an in-memory role list. This is why role checks are fast enough to use liberally throughout your code. The object also maintains references to user preferences, timezone settings, and other session-specific data that affects how the platform behaves for this particular user.
What most developers don't realize is that the GlideUser context can change mid-request through impersonation or gs.setUser() calls. When this happens, ServiceNow doesn't reconstruct the entire security context from scratch—it swaps out the cached user data while maintaining session continuity. This is how features like "Run as this user" in the instance navigator work without requiring re-authentication. However, certain session-level data like the original login timestamp persists across user context switches, which is crucial for audit trails.
The Authentication and Context Lifecycle
- User authenticates through login form, SSO, or API key—ServiceNow validates credentials against
sys_usertable and external identity providers - Platform queries user's direct role assignments from
sys_user_has_roleand group memberships fromsys_user_grmember, then resolves inherited roles through role hierarchy - Session object created with complete security context cached in memory—role lists, group memberships, user preferences, and timezone settings
- GlideUser object instantiated as wrapper around cached security context, available via
gs.getUser()in all server-side execution contexts - Every server-side script execution (Business Rules, Script Includes, UI Actions) receives this user context automatically—no additional setup required
- Session ends through logout, timeout, or termination—cached context destroyed, but audit trail of user actions persists in system logs
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
// Client-side validation before server submission
function validateApproval() {
// Client can't access gs.getUser() - must validate on server
// Only do basic form validation here
var requestState = g_form.getValue('state');
if (!requestState || requestState == 'closed') {
alert('Cannot approve a closed request');
return false;
}
// Set flag for server-side processing
g_form.setValue('u_approval_attempted', 'true');
// Submit to server for role checking and approval logic
return true; // Allow form submission to server
}// Server-side approval processing with GlideUser
(function() {
var currentUser = gs.getUser(); // Get authenticated user context
// Check if user has approval authority for this request type
if (!currentUser.hasRole('approver_user') && !currentUser.hasRole('itil')) {
gs.addErrorMessage('You do not have permission to approve requests');
action.setRedirectURL(current);
return;
}
// Verify user can approve requests from this department
var userDept = currentUser.getDepartmentID();
var requestDept = current.opened_for.department.toString();
if (userDept != requestDept && !currentUser.hasRole('admin')) {
gs.addErrorMessage('You can only approve requests from your department');
action.setRedirectURL(current);
return;
}
// Perform the approval with audit trail
current.state = 'approved';
current.approved_by = currentUser.getID(); // Store approver sys_id
current.approved_on = gs.nowDateTime();
current.work_notes = 'Approved by: ' + currentUser.getDisplayName();
current.update();
gs.addInfoMessage('Request approved successfully');
})();Real-World Scenarios
Dynamic Assignment Based on User Location
Automatically route incidents to the appropriate support group based on the requesting user's location and the current user's regional access rights. This handles global deployments where support teams only handle tickets from specific geographic regions.
// Auto-assign based on requester location and current user's region
(function executeRule(current, previous) {
var submittingUser = gs.getUser(); // User creating the incident
var requesterLocation = current.caller_id.location.toString();
// Get submitter's regional authority from user preferences
var userRegion = submittingUser.getPreference('user.regional_authority');
// Route to appropriate group based on location matching
if (requesterLocation.startsWith('US-') && userRegion == 'north_america') {
current.assignment_group.setDisplayValue('US IT Support');
current.priority = determineRegionalPriority('US', current.impact);
} else if (requesterLocation.startsWith('EU-') && userRegion == 'europe') {
current.assignment_group.setDisplayValue('European IT Support');
current.priority = determineRegionalPriority('EU', current.impact);
// EU requires different escalation rules
current.u_escalation_time = '4'; // 4 hours vs standard 8
} else {
// Default to global team if no regional match
current.assignment_group.setDisplayValue('Global IT Support');
current.work_notes = 'Routed to global team - submitter region: ' +
(userRegion || 'undefined') + ', requester location: ' + requesterLocation;
}
})(current, previous);Watch for edge cases where users submit tickets for other regions—you'll need business rules to handle cross-regional requests. Also consider timezone implications when setting escalation times. User preferences can be null, so always provide fallback logic for undefined regional authorities.
Role-Based Field Visibility Control
Control which fields are visible or editable based on the current user's role and group membership. This prevents sensitive fields from being exposed to users who shouldn't see them, while maintaining a single form definition.
// Server-side field security based on user roles
(function() {
var currentUser = gs.getUser();
var isManager = currentUser.hasRole('incident_manager');
var isAdmin = currentUser.hasRole('admin');
var userGroups = currentUser.getMyGroups(); // Returns ArrayList of group sys_ids
// Hide sensitive fields from non-managers
if (!isManager && !isAdmin) {
g_form.setVisible('business_criticality', false);
g_form.setVisible('financial_impact', false);
g_form.setReadOnly('priority', true); // Can see but not modify
}
// Special handling for security group members
var securityGroupSysId = '1a2b3c4d5e6f7g8h9i0j'; // Replace with actual sys_id
if (userGroups.contains(securityGroupSysId)) {
g_form.setVisible('u_security_classification', true);
g_form.setMandatory('u_security_impact', true);
} else {
g_form.setVisible('u_security_classification', false);
g_form.setVisible('u_security_impact', false);
}
// Department-specific fields - only show to users in same department
var userDept = currentUser.getDepartmentID();
var incidentDept = g_form.getValue('u_affected_department');
if (userDept != incidentDept && !isAdmin) {
g_form.setVisible('u_internal_notes', false);
g_form.addInfoMessage('Some fields hidden due to department restrictions');
}
})();Remember that UI Policies run on form load and on field changes—not continuously. If user roles change mid-session, they won't see the updated field visibility until refresh. Also, getMyGroups() returns sys_ids, not group names, so cache the group sys_ids you need rather than doing name lookups repeatedly.
Approval Workflow with Delegation Logic
Implement intelligent approval routing that considers user hierarchy, delegation settings, and out-of-office status. This ensures approval requests don't get stuck when primary approvers are unavailable.
var ApprovalRouter = Class.create();
ApprovalRouter.prototype = {
initialize: function() {
this.currentUser = gs.getUser();
},
routeApproval: function(requestRecord, approvalType) {
var approver = this._findPrimaryApprover(requestRecord, approvalType);
// Check if primary approver is available
if (!this._isUserAvailable(approver)) {
gs.log('Primary approver ' + approver + ' unavailable, checking delegation');
approver = this._findDelegateApprover(approver, approvalType);
}
// Create approval record with proper context
var approvalGr = new GlideRecord('sysapproval_approver');
approvalGr.initialize();
approvalGr.approver = approver;
approvalGr.sysapproval = requestRecord.sys_id;
approvalGr.type = approvalType;
approvalGr.u_delegated_from = (approver != this._findPrimaryApprover(requestRecord, approvalType)) ?
this._findPrimaryApprover(requestRecord, approvalType) : '';
approvalGr.u_requested_by = this.currentUser.getID();
var approvalSysId = approvalGr.insert();
gs.log('Approval routed to: ' + approver + ' (sys_id: ' + approvalSysId + ')');
return approvalSysId;
},
_findPrimaryApprover: function(requestRecord, type) {
// Implementation depends on your approval matrix
// This is simplified example
if (type == 'manager') {
return requestRecord.opened_for.manager.toString();
} else if (type == 'financial' && requestRecord.cost > 5000) {
return this._getFinanceApprover(requestRecord.cost);
}
return this.currentUser.getManager();
}
};Delegation logic gets complex quickly—consider using ServiceNow's built-in delegation features rather than rolling your own. Also track the original approver even when delegating so you can maintain proper audit trails. Be careful with getManager() calls on users who don't have managers defined—it returns null and can break your approval chains.
The Classic Mistake
Using GlideUser for impersonation without proper cleanup leads to permanent session corruption.
// Business Rule on incident table
(function executeRule(current, previous) {
// Get assignment group manager to auto-approve
var groupGR = new GlideRecord('sys_user_group');
if (groupGR.get(current.assignment_group)) {
var managerSysId = groupGR.getValue('manager');
// WRONG: Impersonate manager to check their permissions
gs.getUser().setUserID(managerSysId);
if (gs.getUser().hasRole('approver_user')) {
current.approval = 'approved';
current.approved_by = managerSysId;
}
// Missing: never reset back to original user
}
})(current, previous);This breaks because setUserID() permanently changes the session's user context. The browser console shows "Access Denied" errors on subsequent requests, and the server log displays "User mismatch" warnings. ServiceNow's session manager cannot reconcile the original logged-in user with the programmatically changed user ID. Once corrupted, the entire user session becomes unstable until logout.
// Business Rule on incident table
(function executeRule(current, previous) {
// Get assignment group manager to auto-approve
var groupGR = new GlideRecord('sys_user_group');
if (groupGR.get(current.assignment_group)) {
var managerSysId = groupGR.getValue('manager');
// CORRECT: Create separate GlideUser for manager
var managerUser = new GlideUser(managerSysId);
if (managerUser.hasRole('approver_user')) {
current.approval = 'approved';
current.approved_by = managerSysId;
}
// Session remains intact - no cleanup needed
}
})(current, previous);Never call setUserID() on gs.getUser() — always instantiate new GlideUser(userSysId) for impersonation.
Performance Rules
- Never call
isMemberOf()in loops over 50 iterations. Each call queriessys_user_grmembertable, causing 5-10 second page load delays and sys admin timeout complaints. - Cache
getMyGroups()results in a variable — don't call it repeatedly in the same script. Each invocation scans entire group membership hierarchy, triggering 15+ database queries. - Avoid
hasRole()in Client Scripts — useg_user.hasRole()instead. Server-side calls from client cause synchronous AJAX requests that freeze the browser for 2-3 seconds. - Don't instantiate new
GlideUser()objects inside Business Rule queries over 100 records. Each constructor loads full user profile from database, causing memory exhaustion and node restart after 500+ instantiations. - Limit
getPreference()calls to 10 per script execution. Each call hitssys_user_preferencetable individually — excessive calls trigger automatic script abortion after 30 seconds. - Never call
gs.getUser()in Scheduled Jobs or fix scripts — returns system user, not the job creator. Use explicitnew GlideUser(userSysId)or all permission checks fail silently. - Cache role check results in UI Policies — calling
hasRole()on every form field change creates 50+ round-trips per page load, making forms unusable on mobile networks. - Don't use
GlideUsermethods in ACL scripts — ServiceNow already provides user context throughgs.getUser(). Additional user object creation doubles ACL evaluation time, causing 10+ second list load delays.
Side Effects & Platform Behavior
- Role checks trigger ACL evaluation chain — Business Rules for
sys_user_roleandsys_user_has_roletables fire, potentially modifying security context mid-execution. - Group membership queries cache results in
g_user_sessiontable — subsequent calls return stale data until session expires or user logs out. - Preference changes via
savePreference()immediately write tosys_user_preferencetable, triggering audit records and notification workflows for preference-watching Business Rules. - User impersonation through
setUserID()writes session change events to System Log > Sessions, visible to administrators monitoring security breaches. - Failed permission checks in Transform Maps cause entire import batch to skip — no error thrown, just silently omitted records in
sys_import_set_runtable. - Department and location lookups hit
cmn_departmentandcmn_locationtables through join queries — broken references cause null returns without warnings. - Client-side
GlideUsercalls in Service Portal break completely — widget throws "object not defined" JavaScript errors visible only in browser developer tools. - Language and timezone changes through
GlideUserrequire session refresh — modified user profile doesn't take effect until next login cycle. - Elevated privilege operations via impersonation bypass normal workflow approval chains — automated approvals may violate compliance requirements.
- Concurrent user object modifications in clustered environments cause
sys_usertable lock contention — multiple users editing profiles simultaneously receive "Record updated by another user" errors.
Debugging When It Breaks
The most common failure shows as blank form fields or missing UI elements with no JavaScript errors. Users see empty dropdown lists or disabled buttons that should be available. Check the browser network tab for 403 responses to AJAX calls — this indicates role check failures are silently blocking content.
For server-side issues, go to System Logs > All and filter by "User" in the Source field. Look for "User context mismatch" or "Invalid user session" messages that appear when setUserID() corrupts sessions. The Script Debugger shows "Cannot read property of undefined" when user objects become invalid mid-execution.
- Check if
gs.getUser().getID()returns expected user sys_id - Verify role assignment in User Administration > Users and verify active=true
- Test with admin user to isolate permission vs code logic issues
- Check System Properties > Session for timeout settings affecting user object persistence
- Look for "Context Security Exception" in application logs when impersonation fails
Quick Reference
- Use
g_userclient-side,gs.getUser()server-side — never mix them - Constructor
new GlideUser(userSysId)creates new instance, doesn't affect session - Method
hasRole()includes inherited roles,hasRoleExactly()checks direct assignment only - Group checks are case-sensitive —
isMemberOf('Service Desk')fails if group name is "service desk" - Method
getMyGroups()returns ArrayList of sys_ids, not group names or GlideRecord objects - User preferences survive session timeout but reset on server restart or cluster failover
- Method
getCompanyID()returns empty string if user.company field is empty, not null - System user context in background jobs has all roles — permission checks always return true
- Inactive users retain cached role data for 24 hours —
hasRole()may return true for disabled accounts - Property
getUserRoles()includes elevated roles from impersonation — usegetMyRoles()for base permissions