What It Is

Access Control Lists (ACLs) are security rules that determine whether users can perform specific operations on tables, records, or fields within ServiceNow. They solve the fundamental problem of data security by enforcing granular permissions beyond simple role-based access, allowing you to control who can read a record, modify specific fields, create new records, or delete existing ones. ACLs evaluate dynamically at runtime, checking user roles, group memberships, and custom conditions to grant or deny access to data.

ACLs live within the System Security application and operate at the data access layer, intercepting every database query before it reaches the table. They're stored in the sys_security_acl table and integrate directly with the GlideRecord API, meaning every database operation—whether from the UI, web services, or server-side scripts—passes through ACL evaluation. The ACL engine sits between your application logic and the database, making it impossible to bypass through clever scripting or alternative data access methods.

The underlying data model treats ACLs as executable rules with four operation types: read, write, create, and delete. Each ACL targets either an entire table (table-level ACL) or specific fields (field-level ACL), with field-level rules taking precedence over table-level ones. The execution environment maintains an ACL cache that refreshes when ACL records are modified, and the platform evaluates ACLs in a specific order based on operation type, user context, and rule specificity.

You cannot function without ACLs in several critical scenarios: multi-tenant environments where different departments need isolated access to the same tables, compliance requirements that mandate field-level security (like PII protection), and complex approval workflows where record visibility changes based on assignment or status. ACLs become essential when role-based security alone cannot express your business rules—when you need to restrict incident updates to only the assigned technician, prevent users from seeing salary information in HR records, or allow managers to view but not modify their team's performance reviews.

Platform owners typically manage the global ACL strategy and design patterns, while application developers create scoped ACLs for their specific applications, and system administrators handle day-to-day ACL maintenance and troubleshooting. The relationship between these roles matters because ACLs can conflict across scopes—a global ACL denying access will override a scoped ACL granting it, and debugging ACL issues often requires understanding which administrator created which rules. Security administrators often own the overall ACL governance, reviewing and approving new ACLs to prevent security gaps or overly permissive access.

Recent ServiceNow releases have introduced significant ACL improvements, particularly around performance and debugging. The Vancouver release enhanced ACL caching mechanisms and introduced better ACL evaluation logging, while Xanadu added ACL simulation tools that let you test access scenarios without impacting live data. The platform also improved ACL inheritance behavior for extended tables, making it clearer how child tables inherit parent table ACLs, and added more granular control over field-level ACL evaluation order.

Where to Find and Configure It

The primary configuration location is System Security > Access Control (ACL), where you create, modify, and delete ACL rules. This module displays the sys_security_acl table in list view, allowing you to filter by table, operation, or active status to find specific rules.

Secondary locations include Studio > Security > Access Controls for scoped application ACLs, and App Engine Studio > Security > Access Controls for low-code ACL creation. Within table configuration, navigate to System Definition > Tables, open a table record, and use the Access Controls related list to view table-specific ACLs.

To see ACLs in action, check the Security Debug module at System Security > Debug Security, which shows real-time ACL evaluation results for specific users and tables. For troubleshooting access issues, use System Logs > System Log > Security to view detailed ACL execution logs when debug logging is enabled.

ℹ️

Scoped applications can only create ACLs for tables within their scope, while global ACLs can control access to any table. Global ACLs always take precedence over scoped ACLs when both apply to the same table and operation.

How It Works Step by Step

ACL evaluation triggers every time a user or system attempts to access data through GlideRecord queries, form loads, list views, or API calls. The ACL engine intercepts these requests before they reach the database, examining the user's roles, group memberships, and the specific operation being performed. The engine maintains a cached list of applicable ACLs for each table and operation type, reducing database lookups and improving performance for frequently accessed data.

The evaluation process follows a grant/deny model where access is denied by default unless an ACL explicitly grants it. Field-level ACLs override table-level ACLs for the same operation, and the first matching ACL that returns true grants access—subsequent ACLs are not evaluated. If no ACLs match or all matching ACLs deny access, the platform falls back to the table's default access controls defined in the sys_db_object record.

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 Execution Order

  1. User initiates a data operation (query, form load, field update) through UI, script, or web service
  2. ACL engine identifies the target table and operation type (read, write, create, delete)
  3. System retrieves cached ACLs matching the table, operation, and user context
  4. Field-level ACLs are evaluated first, followed by table-level ACLs if no field-level rules apply
  5. Each ACL's condition script executes in order of creation, checking user roles and custom logic
  6. First ACL returning true grants access; execution stops and operation proceeds
  7. If all ACLs return false or none exist, access is denied and operation fails silently
Common ACL Condition Script Pattern
// Check if user has specific role
if (gs.hasRole('incident_manager')) {
    answer = true;
}
// Check if user is in record's assignment group
else if (current.assignment_group == gs.getUser().getManagerID()) {
    answer = true;
}
// Check custom field condition
else if (current.state == '1' && current.assigned_to == gs.getUserID()) {
    answer = true;
}
// Deny access by default
else {
    answer = false;
}

// Log for debugging
gs.log('ACL evaluation for user: ' + gs.getUserName() + 
       ' on table: ' + current.getTableName());

Real-World Scenarios

Restricting Incident Updates to Assignment Group Members

Your IT department requires that only members of the assigned group can update incident records, preventing unauthorized changes from other technicians or departments. This prevents ticket tampering and ensures proper workflow control through designated teams.

Incident Write ACL Condition
// Allow if user has incident_manager role (overrides group restriction)
if (gs.hasRole('incident_manager')) {
    answer = true;
}
// Allow if user is member of the assignment group
else if (current.assignment_group.getValue()) {
    var groupGR = new GlideRecord('sys_user_grmember');
    groupGR.addQuery('user', gs.getUserID());
    groupGR.addQuery('group', current.assignment_group);
    groupGR.query();
    
    if (groupGR.hasNext()) {
        answer = true;
    } else {
        answer = false;
    }
} else {
    answer = false;
}

Create this ACL with Operation: write and Table: incident. Watch for performance issues with large assignment groups—consider caching group membership checks. Be careful with unassigned incidents, as they'll become read-only until assignment.

Hiding Sensitive HR Fields from Non-HR Users

HR needs to prevent non-HR staff from viewing salary and personal information on employee records while still allowing access to basic contact details for directory purposes. This meets compliance requirements while maintaining functional access to necessary employee data.

Create field-level ACLs for sys_user.salary, sys_user.ssn, and sys_user.home_phone with Operation: read and condition gs.hasRole('hr_admin') || gs.hasRole('admin'). Set Name descriptively like "HR Sensitive Fields - Read" for easy identification.

Remember that field-level ACLs affect form sections, list columns, and report data—non-HR users won't see these fields anywhere in the platform. Test thoroughly with reference fields that might display restricted data, and consider the impact on integrations that might expect these fields to be readable.

Manager-Only View of Direct Report Performance Reviews

Performance review records should only be visible to the employee's direct manager, HR administrators, and the employee themselves. Other managers and colleagues should not see reviews for employees outside their reporting structure.

Performance Review Read ACL
// Allow HR admins full access
if (gs.hasRole('hr_admin')) {
    answer = true;
}
// Allow employee to view their own review
else if (current.employee == gs.getUserID()) {
    answer = true;
}
// Allow manager to view direct report reviews
else if (current.employee.manager == gs.getUserID()) {
    answer = true;
}
// Allow if user is reviewing manager (for multi-level approvals)
else if (current.reviewing_manager == gs.getUserID()) {
    answer = true;
}
else {
    answer = false;
}

This requires your user records to have accurate manager field relationships and assumes your performance review table has employee and reviewing_manager reference fields. Watch for organizational changes that might leave reviews orphaned when managers change, and consider adding a cleanup process for such scenarios.

The Classic Mistake

⚠️

Creating overly broad ACL rules with operation="*" and no conditions, effectively bypassing security for entire tables.

BAD: Overly Broad ACL
// ACL on incident table with operation="*"
// Type: record
// Active: true
// Admin overrides: false
// Advanced: true

// Condition field (empty - this is the problem!)

// Script:
function canAccess() {
    // Trying to handle all operations in one script
    if (current.operation() == 'read') {
        return gs.hasRole('itil') || current.assigned_to == gs.getUserID();
    }
    if (current.operation() == 'write') {
        return gs.hasRole('incident_manager');
    }
    return false; // This gets hit for create/delete operations
}

This fails because ServiceNow evaluates the single wildcard ACL for ALL operations, creating unpredictable behavior where users randomly lose access to records they should see. The script attempts to handle multiple operations but doesn't account for edge cases like before queries or field-level operations, and the current.operation() method doesn't always return what you expect in list contexts. Users see inconsistent access - they can open a record directly but can't see it in lists, or they can read but mysteriously can't update fields they should control.

GOOD: Specific Operation ACLs
// ACL 1: incident table, operation="read"
// Type: record
// Condition: gs.hasRole('itil')
// Script: (empty - condition handles it)

// ACL 2: incident table, operation="read" 
// Type: record
// Condition: (empty)
// Script:
if (current.assigned_to == gs.getUserID() || current.caller_id == gs.getUserID()) {
    return true;
}
return false;

// ACL 3: incident table, operation="write"
// Type: record  
// Condition: gs.hasRole('incident_manager')
// Script: (empty)

// ACL 4: incident table, operation="create"
// Type: record
// Condition: gs.hasRole('itil')
// Script: (empty)
💡

Create separate ACL records for each operation (read, write, create, delete) rather than using operation="*" - this makes ACL evaluation predictable and debugging straightforward.

When to Use This vs Alternatives

ACLs are the correct choice when you need to control data access at the record or field level based on user roles, record state, or relationships. They're the only security mechanism that operates at the database query level, filtering results before they reach the user interface.

When ACLs Are the Right Choice

Use ACLs when you need row-level security that persists across all access methods - forms, lists, APIs, and integrations. Business Rules and Client Scripts can be bypassed through direct database access or API calls, but ACLs cannot. ACLs are also essential when you need field-level security that prevents users from even seeing sensitive data like salary information or confidential notes.

When to Use UI Policies Instead

Choose UI Policies over ACLs when you need dynamic field visibility or read-only behavior that changes based on form state rather than security requirements. UI Policies are better for workflow-driven field control - like making fields mandatory when a ticket reaches a certain state - because they don't impact database queries or API performance. Use UI Policies when the field restriction is about business process, not data security.

When You Need Both ACLs and Business Rules

Combine ACLs with Business Rules when you need security enforcement plus data validation or workflow automation. ACLs handle the "who can access what" while Business Rules manage "what happens when they do." For example, use ACLs to ensure only managers can approve requests, then use Business Rules to automatically update related records and send notifications when approval occurs.

Platform Interactions & Side Effects

  • ACL evaluation results are cached in user sessions, meaning ACL changes don't take effect until users log out and back in or their session expires
  • Business Rules execute after ACL evaluation, so before Business Rules can't modify records to make them ACL-compliant - the ACL denial happens first
  • REST API calls respect ACLs by default, but Web Service calls bypass them unless you explicitly enable ACL enforcement in the SOAP processor
  • Update Set deployment can fail silently if target instance ACLs prevent the update set user from modifying records during data preservation
  • Notification email templates can't access fields blocked by ACLs, resulting in blank values in emails even when the notification runs with elevated privileges
  • ServiceNow writes ACL evaluation failures to the syslog table with source security but only when the glide.security.log_acl_reads property is enabled
  • List personalization and saved filters can become permanently inaccessible if they reference fields later protected by ACLs, with no user-visible error message
  • Scripted REST APIs inherit the calling user's ACL restrictions unless you explicitly impersonate a user with gs.getUser().setRole() or use gs.setProperty('glide.security.enforce_acls', 'false')
  • Transform Maps and Import Sets completely bypass ACLs during data processing, potentially creating records that users can't subsequently access through normal UI
  • Performance impact scales with ACL script complexity - each database query re-evaluates applicable ACLs, so complex scripts on frequently-accessed tables can cause significant slowdown

Debugging and Troubleshooting

The most common ACL failure symptoms appear as inconsistent data access where users can see records in one context but not another. Users report seeing empty lists that should contain data, or they can access records via direct links but those same records don't appear in table views. Administrators often see reports of "missing" data that's actually just ACL-filtered, and users getting Access Denied errors when trying to update fields they believe they should control.

Start debugging at System Logs > System Log > All filtered by source security, but remember that ACL denials aren't logged by default. Enable the glide.security.log_acl_reads system property to see detailed ACL evaluation logging. The Security module under System Security > Access Control (ACL) shows all ACLs in evaluation order, and the Security Debugging module provides real-time ACL evaluation tracing.

Look for specific error messages like "ACL Exception Access Denied" in browser developer console, or "Security constraints restrict this operation" in API responses. The sys_security_acl_debug table captures detailed ACL evaluation when debug logging is enabled, showing which ACLs were evaluated and their results. Script errors in ACL conditions appear in System Logs with the message "Error in ACL script" followed by the table and field names.

Diagnostic Checklist:

  • Test ACL behavior by impersonating the affected user - don't rely on having elevated privileges
  • Verify ACL evaluation order in System Security > Access Control (ACL) - order determines which ACL wins
  • Check if Admin overrides is enabled - this bypasses ACLs for users with admin role
  • Enable glide.security.log_acl_reads system property and reproduce the issue to capture evaluation logs
  • Clear user session cache by logging out and back in after ACL changes
  • Test the same operation through different interfaces (form, list, API) to identify context-specific issues
  • Review related table ACLs in the inheritance hierarchy - child table ACLs can be overridden by parent table rules

Quick Reference

  • ACLs with Admin overrides enabled are completely bypassed for users with the admin role, making security testing with admin users meaningless
  • ServiceNow processes ACLs in strict numeric order (0-999), then alphabetically - there's no "priority" field, so order value is critical for conflicting rules
  • Field-level ACLs override record-level ACLs, so you can grant table access but still block sensitive fields like user_password or salary
  • The current object in ACL scripts refers to the record being accessed, not the record being updated - critical distinction for before queries
  • Maximum of 1000 ACL rules can be evaluated per database query - exceeding this limit causes automatic denial regardless of rule logic
  • ACL scripts execute in a restricted scope - you can't use GlideRecord queries or call other scripts, only basic gs methods and current object properties
  • Table inheritance affects ACL evaluation - ACLs on parent tables like task apply to child tables like incident unless explicitly overridden
  • ACL evaluation results are cached per user session for up to 30 minutes (default) - controlled by glide.security.acl_cache_ttl system property
  • Mobile app ACL evaluation differs from web UI - some complex scripts fail in mobile context due to limited JavaScript scope
  • Cross-scope ACLs (scoped app accessing global table) require explicit All application scopes setting or they're silently ignored