ACLs in ServiceNow: The Complete Developer and Admin Guide

Access Control Lists are the primary security mechanism in ServiceNow. They control who can read, write, create, and delete records and fields — and when improperly configured, they cause confusing access issues that are hard to diagnose and potentially dangerous security gaps. This guide covers everything: the ACL evaluation model, all ACL types, the three conditions, script ACLs, field-level security, debugging tools, common security mistakes, and production best practices.

What is an ACL and why it matters

An ACL (Access Control List) is a rule that determines whether a user can perform a specific operation on a specific object — a table, a record, a field, or a UI element. Every time a user views a list, opens a record, edits a field, or clicks a UI action button, ServiceNow evaluates applicable ACLs to determine whether the operation is allowed.

The default stance is deny. If no ACL matches a user's access attempt, access is denied. ACLs grant access — they do not restrict from a permissive baseline. This secure-by-default model means that custom tables and fields you create are protected until you explicitly grant access.

ACL types

ACLs can be applied at five different levels:

  • Table-level ACLs — control access to an entire table for all records. The most common type. Example: who can read the incident table at all.
  • Record-level ACLs — control access to specific records based on the record's data. Uses script conditions. Example: users can only see incidents assigned to their group.
  • Field-level ACLs — control access to a specific field on a table. Example: only HR managers can read or write the salary field on sys_user.
  • UI Action ACLs — control visibility of buttons, context menu items, and form actions. Example: only change managers can see the "Approve Change" button.
  • Business Rule ACLs — rarely used directly; controls whether a Business Rule can be seen in the development interface.

ACL operations

Each ACL is associated with one of these operations:

  • read — can the user view this record or field?
  • write — can the user modify this field?
  • create — can the user insert new records on this table?
  • delete — can the user delete records from this table?
  • execute — used for UI Actions — can the user see and click this button?

A user needs separate ACL permissions for each operation. Having read access to a table does not automatically grant write access.

How ACL evaluation works — the evaluation sequence

Understanding evaluation order explains why some ACLs seem to be ignored and why unexpected access issues occur.

  1. ServiceNow identifies all ACLs that could match the object being accessed (e.g., all read ACLs on the incident table or the incident.state field)
  2. ACLs are sorted by specificity — more specific ACLs evaluate first: field-level before table-level, specific role before wildcard role
  3. Each matching ACL is evaluated — Role AND Condition AND Script must all pass
  4. The first ACL that explicitly grants access allows the operation
  5. If no ACL grants access, access is denied

Specificity rules:

  • incident.state (field ACL) evaluates before incident.* (all fields ACL)
  • incident (table ACL) evaluates before task (parent table ACL)
  • A user needs to pass both the table read ACL and the field read ACL to see a field's value

The three conditions — all must pass

Each ACL has three independent conditions. All three must evaluate to true for the ACL to grant access. If any one fails, the ACL denies access regardless of the others.

1. Roles

The user must have at least one of the roles listed on the ACL. If no roles are specified, any authenticated user passes the role check. If roles are specified, the user must have at least one of them — it is an OR condition, not AND.

// ACL roles: itil, admin
// User passes if they have itil OR admin (not required to have both)

2. Condition

A filter condition evaluated against the record. Works like an encoded query condition — if the record does not match the condition, the ACL does not apply. Leave empty for "always apply."

// ACL condition on incident table:
// State = New OR State = In Progress
// This ACL only applies to open incidents — not resolved or closed ones

3. Script

A server-side script that returns true to grant access or false to deny. Use for dynamic access control that cannot be expressed as a role check or filter condition alone.

// ACL Script field — must evaluate answer to true or false
// Example: Allow write access only to assigned agent or admin

// Method 1 — set answer variable
answer = gs.hasRole('itil_admin') || (current.assigned_to == gs.getUserID());

// Method 2 — use a function that returns true/false
(function() {
    // Allow if user is the assigned agent
    if (current.assigned_to == gs.getUserID()) return true;
    // Allow if user has the admin role
    if (gs.hasRole('admin')) return true;
    // Allow if user is in the assignment group
    var gr = new GlideRecord('sys_user_grmember');
    gr.addQuery('user', gs.getUserID());
    gr.addQuery('group', current.assignment_group);
    gr.query();
    return gr.next();
})()

Field-level ACLs

Field-level ACLs are one of the most commonly missed security requirements. Securing a table with read ACLs does not protect individual fields on that table. A user with table read access can see all fields unless specific field-level ACLs restrict them.

// Table: sys_user (Users)
// Table-level read ACL: authenticated users can read
// But without field-level ACLs, everyone can see salary, SSN, personal data

// Field-level ACL examples needed:
// sys_user.u_salary — read: hr_manager role only
// sys_user.u_ssn — read: admin role only
// sys_user.u_medical_info — read: hr_medical role only

Always audit sensitive fields when setting up table ACLs. Ask: if a user can read this record, are there fields they should not see? Those fields need their own field-level ACLs.

Script ACL patterns

Record ownership — user can only access their own records

// ACL on: incident, operation: read
// Allow if user is the caller, assigned agent, or has itil role
answer = gs.hasRole('itil') 
      || current.caller_id == gs.getUserID()
      || current.assigned_to == gs.getUserID();

Group-based access — user in the assignment group

// Allow if user is in the assignment group of the record
answer = gs.hasRole('admin') || isInGroup(current.assignment_group.toString());

function isInGroup(groupId) {
    if (!groupId) return false;
    var gr = new GlideRecord('sys_user_grmember');
    gr.addEncodedQuery('user=' + gs.getUserID() + '^group=' + groupId);
    gr.query();
    return gr.next();
}

Time-based access — access only during business hours

// Allow admin operations only during business hours
var hour = new GlideDateTime().getLocalTime().getByFormat('HH');
var hourInt = parseInt(hour);
answer = gs.hasRole('admin') && hourInt >= 8 && hourInt < 18;

Performance in ACL scripts — critical warning

ACL scripts execute on every single record access. If an ACL script contains a GlideRecord query, that query runs for every record in every list view, every API response, and every form load that involves records from that table.

On an incident list with 1,000 results, a GlideRecord query inside the incident ACL script runs 1,000 times. This is a significant performance problem.

// BAD — GlideRecord query inside ACL script runs on every single record
answer = gs.hasRole('itil');
var gr = new GlideRecord('u_access_config'); // This runs per-record
gr.addQuery('user', gs.getUserID());
gr.query();
if (gr.next()) answer = true;

// BETTER — use gs.hasRole() and current field comparisons only
// These are evaluated without additional database queries
answer = gs.hasRole('itil') || current.caller_id == gs.getUserID();

// If you must query, cache the result in a session variable
var cacheKey = 'acl_cache_' + gs.getUserID();
var cached = gs.getSessionClientData(cacheKey);
if (cached !== null) {
    answer = (cached == 'true');
} else {
    var hasAccess = checkAccessFromDB(gs.getUserID());
    gs.putSessionClientData(cacheKey, hasAccess.toString());
    answer = hasAccess;
}

Debugging ACL issues

The Security Debug plugin

The fastest way to diagnose ACL issues:

  1. Navigate to System Diagnostics > Security Policy Check (or search "security" in navigator)
  2. Enter the sys_id of the record the user cannot access
  3. Enter the user's sys_id (impersonate them or use their sys_id)
  4. Select the operation (read, write, create, delete)
  5. Click Evaluate

The output shows every ACL that was evaluated, in what order, and which condition failed — the role check, the condition, or the script. This tells you exactly why access was denied in under a minute.

URL debug parameter

Add &sysparm_security_debug=true to any URL to see ACL debug output directly in the page. This shows which ACLs matched for the records on that page, inline in the response.

Testing as the actual user

Testing ACLs as admin with impersonation is unreliable because admin bypasses many ACL checks — including ACLs that do not list admin in their roles (admin has implicit access to most things). Always test in a separate browser session logged in as the actual user, or as a test account with exactly the same roles as the user experiencing the issue.

// This is the most common ACL testing mistake:
// Developer impersonates user A, tests as admin → "I can access it, so it works"
// User A logs in → "I cannot access it"
// Admin implicit permissions bypass ACLs that user A would fail

// Correct testing:
// Log in directly as user A (or a test account with identical roles)
// Test the exact operation that fails

Common security mistakes

Mistake 1 — Using admin as ACL bypass

// Dangerous pattern
answer = gs.hasRole('admin') || someCondition;
// If admin access scope expands, this ACL exposes data unintentionally

// Better — be explicit about which roles should have access
answer = gs.hasRole('itil_admin') || someCondition;

Mistake 2 — Missing field-level ACLs on sensitive data

Always add field-level ACLs for: salary, compensation, SSN, medical data, personal identification, passwords/credentials, private notes.

Mistake 3 — ACL conditions too broad

An ACL with no condition applies to every record on the table — including records from other companies in multi-tenant deployments, historical records, and records in states the ACL was not intended to cover. Always add conditions that scope the ACL to exactly the records it should apply to.

Mistake 4 — Not reviewing ACLs during upgrades

ServiceNow upgrades can add new fields to existing tables, modify role definitions, or change how built-in ACLs evaluate. Review security-critical ACLs after every upgrade to ensure they still behave as expected.

Mistake 5 — Over-relying on UI visibility for security

Hiding a field in a form view or making it read-only with a UI Policy does not prevent access via the REST API or direct table access. Security must be enforced at the ACL layer — UI restrictions are for usability, not security.

ACL best practices

  • Create custom roles for your applications — do not re-use built-in roles for custom access control. Create a role like x_myapp_user and grant it exactly the access needed.
  • Document what each custom role grants — add a description to every custom role record: "Grants read access to customer service tables and write access to case records assigned to the user's group."
  • Keep ACL scripts lightweight — gs.hasRole() and current field comparisons only. No GlideRecord queries unless cached.
  • Test as the actual user — not as admin with impersonation.
  • Review ACLs in every release — they accumulate technical debt. Old ACLs that no longer apply should be cleaned up.
  • Never leave admin-created tables without ACLs — any table created in the global scope without explicit ACLs is accessible to any user with the admin role.

Related guides:

Security architecture reading: Roles vs Groups — the distinction that governs ACL access · ACL Security Guide — deeper coverage of scripted ACLs · Scoped applications — how ACL scope works in custom apps · Business Rules — security logic that runs on record operations · Debugging — diagnosing ACL evaluation issues · Interview questions — ACL patterns tested at mid and senior level

Pass your ServiceNow interview

The NowSpectrum Interview Prep Kit covers 50 questions and answers including ACLs, security, roles, and platform fundamentals — everything interviewers actually test.

Get the Interview Prep Kit →
← Back to all posts