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

  1. User authenticates through login form, SSO, or API key—ServiceNow validates credentials against sys_user table and external identity providers
  2. Platform queries user's direct role assignments from sys_user_has_role and group memberships from sys_user_grmember, then resolves inherited roles through role hierarchy
  3. Session object created with complete security context cached in memory—role lists, group memberships, user preferences, and timezone settings
  4. GlideUser object instantiated as wrapper around cached security context, available via gs.getUser() in all server-side execution contexts
  5. Every server-side script execution (Business Rules, Script Includes, UI Actions) receives this user context automatically—no additional setup required
  6. Session ends through logout, timeout, or termination—cached context destroyed, but audit trail of user actions persists in system logs
Free Newsletter

Enjoying this? Get one deep-dive per week.

Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.

No spam · Unsubscribe anytime

The Core Pattern

UI Action — Approve Request.js
// 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
}
UI Action — Approve Request (Server).js
// 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.

Business Rule — Incident Assignment.js
// 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.

UI Policy Script — Incident Form Security.js
// 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.

Script Include — ApprovalRouter.js
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.

Anti-pattern — Do Not Use This.js
// 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.

The Fix.js
// 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

  1. Never call isMemberOf() in loops over 50 iterations. Each call queries sys_user_grmember table, causing 5-10 second page load delays and sys admin timeout complaints.
  2. 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.
  3. Avoid hasRole() in Client Scripts — use g_user.hasRole() instead. Server-side calls from client cause synchronous AJAX requests that freeze the browser for 2-3 seconds.
  4. 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.
  5. Limit getPreference() calls to 10 per script execution. Each call hits sys_user_preference table individually — excessive calls trigger automatic script abortion after 30 seconds.
  6. Never call gs.getUser() in Scheduled Jobs or fix scripts — returns system user, not the job creator. Use explicit new GlideUser(userSysId) or all permission checks fail silently.
  7. 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.
  8. Don't use GlideUser methods in ACL scripts — ServiceNow already provides user context through gs.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_role and sys_user_has_role tables fire, potentially modifying security context mid-execution.
  • Group membership queries cache results in g_user_session table — subsequent calls return stale data until session expires or user logs out.
  • Preference changes via savePreference() immediately write to sys_user_preference table, 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_run table.
  • Department and location lookups hit cmn_department and cmn_location tables through join queries — broken references cause null returns without warnings.
  • Client-side GlideUser calls in Service Portal break completely — widget throws "object not defined" JavaScript errors visible only in browser developer tools.
  • Language and timezone changes through GlideUser require 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_user table 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_user client-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 — use getMyRoles() for base permissions