Sitemap

ACLs in Depth: Table, Field and Record-Level Access Control

14 min readOct 21, 2026

--

ServiceNow Developer Series, Part 8

Press enter or click to view image in full size

Series so far: Part 1: Script Include Basics · Part 2: Business Rules · Part 3: GlideAjax in Depth · Part 4: Returning Multiple Values (JSON) · Part 5: GlideRecord Deep Dive · Part 6: Client Scripts vs UI Policies vs Data Policies · Part 7: Debugging Toolkit · Part 8: ACLs in Depth (this post)

1. Why this topic matters

Access Control Lists, or ACLs, have appeared in almost every part of this series, yet we never explained them properly:

  • Part 4 told you to use GlideRecordSecure so "ACLs are respected."
  • Part 5 reminded you that browser-reachable code must enforce ACLs.
  • Part 6 said “ACLs sit above both” when a UI Policy and a Client Script disagree.
  • Part 7 used Debug Security to explain “no access” problems.

This post fills the gap. ACLs are the part of ServiceNow that answers one question on every request: “Is this user allowed to do this, to this object, right now?”

Get them right and your data is protected on forms, lists, imports, REST calls and scripts. Get them wrong and you get one of two painful outcomes: users who cannot do their jobs, or users who can see data they should never see. You will meet both in real projects.

By the end you will understand:

  • what an ACL is made of
  • how ServiceNow decides which ACLs apply, and in what order
  • the difference between table-level, field-level and record-level control
  • how to write ACL scripts that are safe and fast
  • how ACLs relate to the Client Scripts, UI Policies and Data Policies from Part 6
  • how to debug an ACL when access is not what you expected

2. The mental model

ServiceNow’s platform security documentation describes ACLs as the rules that secure objects such as tables, fields, records, UI pages and scripted REST APIs by defining who can perform specific operations on them.

Think of an ACL as a gate with three questions written on it:

  1. What is being protected? (a table, a field, a record, a REST endpoint)
  2. Which action is being attempted? (read, write, create, delete, execute)
  3. Who gets through, and under what condition? (roles, data conditions, a script)

Two principles matter more than any other:

  • Deny by default. The ACL engine denies access when an ACL is empty or invalid. An empty ACL is one with no role, no security attribute, no data condition and no script.
  • Everything in one ACL must pass. To get through a matching ACL, a user must satisfy every condition it lists. Failing any one condition blocks access through that ACL.

One nuance worth knowing: if there are no matching ACLs at all for an object and operation, the documentation says the system grants access. In practice this is rare, because the platform ships with default ACLs that protect all record operations.

3. Anatomy of an ACL

Press enter or click to view image in full size

3.1 Decision type: Allow-If and Deny-Unless

Every ACL has a decision type:

  • Allow-If: access is allowed if the conditions pass.
  • Deny-Unless: access is denied unless the conditions pass.

Allow-If is the familiar style: “users with the itil role may update incidents." Deny-Unless is a stricter style: "everyone is denied unless they meet this condition." Training material describes the Decision Type field as introduced in the Xanadu release and notes that it also affects the order in which ACLs are evaluated relative to each other, so check the Deny-Unless documentation for your release before relying on exact precedence.

3.2 Type, name and operation

The type and name together identify the object being secured. The documentation gives these examples:

  • incident.[--None--]: the Incident table
  • incident.active: the Active field on the Incident table
  • a record-type ACL with Applies-To: Priority = P1: only Priority 1 incidents
  • REST_Endpoint named user_role_inheritance: a scripted REST API

The operation is the action secured. Records support several (read, write, create, delete), while an object like a REST endpoint supports just one (execute).

3.3 Applies-to versus data condition

These two sound similar but do different jobs:

  • Applies-to decides whether the ACL applies to a record at all. If it is empty, the ACL applies to all records. Applies-to is case sensitive.
  • Data condition is a test after the ACL has been selected, checking values on the record.

Use Applies-to to target a subset of records. Use a data condition to evaluate a rule for the records already in scope.

3.4 Roles, security attributes and scripts

An ACL can require:

  • one or more roles (the user needs at least one; an empty list passes)
  • one or more security attributes (for example, the user is authenticated)
  • data conditions on the record
  • a script that returns true or false, or sets the answer variable

3.5 Who can edit ACLs

Normal administrators can view and debug ACLs. To create or change one, an administrator must elevate to the security_admin role. This is intentional: ACLs are the foundation of your security model.

4. How ServiceNow evaluates ACLs

Press enter or click to view image in full size

4.1 The evaluation steps

  1. A user requests an object and operation (for example, read incident.assignment_group).
  2. The system finds the matching ACLs, working from the most specific to the least specific.
  3. For a matching ACL, it checks the role, the security attribute, the data condition and the script. All must pass.
  4. If the ACL passes, access is granted.
  5. If it fails, the system tries the next matching ACL in the processing order.
  6. If no matching ACL passes, access is denied.

4.2 Records need two passes

For records, ServiceNow requires both of these to pass:

  1. the first ACL that matches the field, and
  2. the first ACL that matches the table.

This is why a user can be allowed to open an incident (table ACL passes) but still see a read-only or empty field (field ACL fails). It is also why the usual cause of “I can see the record but not this field” is a field-level ACL whose condition or script is failing.

4.3 Several ACLs at the same level

If two or more ACLs match at the same point in the processing order, the user needs to pass any one of them. The documentation’s example: with two field ACLs on incident.number, a user who passes one has access to the field, regardless of failing the other.

4.4 Wildcard ACLs

Wildcard ACLs (written with *) act as the safety net:

  • By default the platform includes wildcard table rules for create, read, write and delete, and wildcard field rules for a few operations.
  • When you create a new table, create ACLs for it unless you intend to rely on the wildcard ACLs.
  • The Security manager default behavior property (glide.sm.default_mode) controls whether users can access objects that match only the wildcard table ACLs. When set to deny, only administrators can.
  • Wildcard table ACLs can be created only in the global scope.

4.5 ACLs in scoped applications

  • You can create ACLs for objects in the same scope as the ACL.
  • If the table is in a different scope, you cannot use a script to evaluate conditions in that ACL.
  • You cannot create or modify ACLs for objects in a different scope from the application you have selected.

5. Pre-query and post-query checks

ACLs run twice per query, and the two checks know different things.

Press enter or click to view image in full size
  • Pre-query check: before the database query runs, the instance checks the ACLs for each field in the table to decide which fields the user may access. This check looks only at the user’s roles. It cannot see record data, so scripts and conditions that depend on a record’s contents are not evaluated at this stage. If access fails here, the value is not shown.
  • Post-query check: after the query, the instance checks each returned record. Now there is context, so the role, the condition and the script are all evaluated. If access fails here, the value is hidden, but the user still sees the field label if their roles allow the field.

This explains a common puzzle: “Why do I see the field label but the value is empty?” Because the pre-query check passed (roles are fine) and the post-query check failed (a condition or script said no for that record).

What the user experiences when an ACL denies access

Press enter or click to view image in full size

One special case from the documentation: a create ACL with a condition requiring a field to hold a specific value may evaluate to false, because fields on a new record are considered empty until the record is saved. For create ACLs, prefer a role check or a script over conditions that depend on field values.

6. Writing ACL scripts

When roles and conditions cannot express your rule, add a script. The script must evaluate to true or set the answer variable.

// Write ACL on incident.assigned_to: only the assignee may edit
answer = (current.getValue('assigned_to') === gs.getUserID());
// Write ACL on the incident table: no edits after Resolved
answer = (parseInt(current.getValue('state'), 10) < 6);

Rules for ACL scripts:

  1. Keep them small and fast. An ACL can run for every row in a list and every field on a form. A script that runs a GlideRecord query for each check can slow pages down. Security guidance for ACL scripts lists queries inside ACL scripts as a performance risk, and suggests using conditions or caching results instead.
  2. Prefer roles and data conditions first, because they are easier to read, audit and debug than script logic.
  3. Always set answer. Make the true and false paths explicit.
  4. Do not write the literal answer = true. The documentation lists an ACL script that is just answer=true or true as an invalid ACL, and invalid ACLs deny access by default.
  5. Use getValue() for comparisons, the same habit from Part 5, rather than comparing GlideElement objects.
  6. Mind scope limits. You cannot use script conditions when the table lives in a different scope from the ACL.

7. Common design patterns

Press enter or click to view image in full size

A worked example: protect the Justification field

Requirement: only users with the itil role may read u_justification on Incident (the example field from Part 6).

  1. Elevate to the security_admin role.
  2. Open System Security > Access Control (ACL) and create a new ACL.
  3. Set Type to record, Operation to read, and Name to incident and u_justification.
  4. Set the decision type to Allow-If, and add itil to Requires role.
  5. Save, then impersonate a user without itil and confirm the field disappears. Impersonate a user with itil and confirm it appears.

No script, no browser code, and the protection applies on forms, lists, reports and API calls.

8. ACLs versus the tools from Part 6

This is the most important design rule for security work:

  • ACL: access control. Enforced on the server for every path.
  • Data Policy: data integrity. Mandatory and read-only rules, enforced on the server.
  • UI Policy and Client Script: user convenience. They run in the browser and can be bypassed.

If a rule is about who may see or change data, it belongs in an ACL. Hiding a field with a UI Policy or g_form.setDisplay() only hides it on that form. The data is still reachable through a list, a report, or a REST call.

What about Query Business Rules?

A community article from a ServiceNow performance support engineer compares Before Query Business Rules with ACLs. Its conclusions are worth remembering:

  • Unlike ACLs, Before Query Business Rules also apply to queries made from scripts.
  • They can help performance in some cases, but they can also cause performance problems.
  • You cannot debug them with the Debug Security module, while ACLs can be debugged easily.
  • Roles, ACLs, domain separation and other out-of-the-box security features are the preferred and recommended way to apply data security and segregation.

9. ACLs and your server code (Parts 4 and 5)

A server script is not automatically limited by the current user’s ACLs the way a form is. That makes it important to choose the right API.

  • GlideRecord reads and writes data, but it relies on ACL enforcement through configuration.
  • GlideRecordSecure inherits from GlideRecord, performs the same functions, and also enforces ACLs.
  • You can also check permissions by hand with canRead(), canWrite(), canCreate() and canDelete().
var gr = new GlideRecord('incident');
if (gr.get(sysId) && gr.canRead()) {
// safe to use this record for this user
}
// Field level check
if (gr.assigned_to.canWrite()) {
gr.setValue('assigned_to', newAssignee);
}

Client-callable Script Includes

For client-callable Script Includes (Parts 3 and 4), the documentation says users must meet the conditions of two ACLs: all wildcard ACLs for that object type (if any exist), and the first ACL matching the Script Include’s name. By default there are no wildcard rules for this object type, but if you create one it applies to all objects of that type.

Get Shubh Prakash Shukla’s stories in your inbox

Join Medium for free to get updates from this writer.

ServiceNow’s Security Center also documents the system property glide.script.secure.ajaxgliderecord, which performs ACL validation when server-side records are accessed through GlideAjax from a client script. It recommends GlideRecordSecure when querying sensitive data.

10. Debugging ACLs (Part 7 in practice)

When access is not what you expected, do not guess. Use the tools.

10.1 Turn on ACL debugging

Under System Security, use Debug Security Rules. The documentation describes field-level debugging indicators and ACL rule output messages at the bottom of every list and form. Each message shows:

  • a PATH that identifies the rule in the format <rule type>/<rule name>/<operation>
  • timing, a unique identifier for the rule, the evaluation context, return codes and a summary

You can click an ACL’s name in the output to open that ACL. Use Stop Debugging under System Security when you are done.

10.2 Impersonate the affected user

After enabling ACL debugging, impersonate another user to see which ACLs that user passes and fails. When you impersonate someone, you see only what that user can see. To make debugging easier, read-only access to a few ACL-related tables is allowed while impersonating (controlled by the glide.security.access_acl_as_impersonator property). If it is off, you may need a second session as admin or security_admin.

Compare an affected user with an unaffected one. A single missing role is enough to block a role-based ACL.

10.3 Use Access Analyzer

Under System Security > Access Analyzer you will find Evaluate Access, Compare Access and Access Simulator. A community guide calls this the fastest route when users cannot see what they should, and notes that ACLs evaluate in two gates, table-level first, then field-level.

10.4 Gotchas that waste hours

  • The standard debug security output does not display on portal pages.
  • Reports and dashboards can fail with report_view messages. report_view ACLs are a separate layer from regular read ACLs.
  • Upgrades and patches can add new ACL records that restrict access.
  • Admins can still see data after you clear “Admin overrides.” The admin role inherits most normal roles, so admins can pass a role check legitimately.
  • An IAccessHandler is a non-modifiable platform check that can grant or deny access without evaluating ACLs.

11. Common mistakes and fixes

Press enter or click to view image in full size

12. Before and after: protecting a sensitive field

Before (convenience, not security):

// Client Script: hides the field in the form UI only
function onLoad() {
if (!g_user.hasRole('itil')) {
g_form.setDisplay('u_justification', false);
}
}

Problems: the field is hidden only on this form. A list view, a report, an export or a REST call can still return the value. And the check relies on the browser.

After (real protection):

  • Create a read ACL on incident.u_justification that requires the itil role.
  • Delete the client script.

The value is now blocked on forms, lists, reports and APIs, and the rule is visible in one auditable place.

13. Best practices

  1. Start from least privilege. Grant only what each role needs.
  2. Use roles and data conditions before scripts. They are faster and easier to audit.
  3. Protect new tables explicitly. Create read, write, create and delete ACLs rather than relying only on wildcards.
  4. Protect client-callable Script Includes with an ACL and a required role.
  5. Use GlideRecordSecure or canRead()/canWrite() in code reachable by users.
  6. Never treat browser rules as security. UI Policies and Client Scripts are convenience only.
  7. Test as real users, by impersonation, never only as an administrator.
  8. Audit regularly. Look for ACLs that reference roles that no longer exist, and review new ACLs after upgrades.
  9. Name and document your ACLs so the next developer understands the intent.
  10. Keep ACL scripts light, with no database queries inside them where possible.

14. Final checklist

Design

  • [ ] The rule is about access, so it is an ACL, not a Client Script or UI Policy
  • [ ] The object, operation and required roles are clearly defined
  • [ ] Roles and data conditions are used before scripts
  • [ ] Create ACLs avoid conditions on field values that do not exist yet

Scripts

  • [ ] The script sets answer on every path
  • [ ] It does not use the literal answer = true
  • [ ] It uses getValue() for comparisons
  • [ ] It runs no heavy queries

Coverage

  • [ ] New custom tables have their own ACLs
  • [ ] Client-callable Script Includes have an ACL with a required role
  • [ ] Server code uses GlideRecordSecure or permission checks where users trigger it

Testing

  • [ ] Tested by impersonating users with and without the roles
  • [ ] ACL debugging used to read the PATH output
  • [ ] Access Analyzer used to compare access when users report differences
  • [ ] ACL debugging turned off afterward

15. Key takeaways

  1. ACLs are server-side access control. They protect forms, lists, reports and APIs alike.
  2. All conditions of one ACL must pass, and for records both the field ACL and the table ACL must pass.
  3. Empty or invalid ACLs deny by default, including a script that is just answer = true.
  4. ACLs run in two stages. The pre-query check sees only roles, and the post-query check sees the record.
  5. Hiding a field in the browser is not security. Use an ACL.
  6. Debug with the right tools: Debug Security Rules, impersonation and Access Analyzer.

Sources

  1. ServiceNow Docs, Explore Access Control Lists (current platform security documentation: components, decision types, applies-to, deny by default, evaluation, pre/post query checks, wildcard and scoped rules): https://www.servicenow.com/docs/r/platform-security/access-control/exploring-access-control-list.html
  2. ServiceNow Docs, Exploring Access Control Lists (Yokohama): https://www.servicenow.com/docs/r/yokohama/platform-security/access-control/exploring-access-control-list.html
  3. ServiceNow Docs, ACL debugging tools (debug output, impersonation, glide.security.access_acl_as_impersonator): https://www.servicenow.com/docs/r/xanadu/platform-security/access-control/c_AccessControlRulesDebug.html
  4. ServiceNow Docs, Permission evaluation (Access Analyzer evaluation hierarchy): https://www.servicenow.com/docs/r/1nuU0tTksWZZbL8T4WKSDg/nihfgdIQxQ4~ojIXX8CK3Q
  5. ServiceNow Docs, Require AJAXGlideRecord ACL checking (Security Center; glide.script.secure.ajaxgliderecord, GlideRecordSecure, canRead() and related methods): https://www.servicenow.com/docs/r/1nuU0tTksWZZbL8T4WKSDg/FDmpCLqJOhMWTHvUffHmuw
  6. ServiceNow Community, Access control: when users can’t see what they should (Access Analyzer, report_view layer, upgrades, Admin Overrides): https://www.servicenow.com/community/platform-privacy-security/access-control-when-users-can-t-see-what-they-should/ta-p/3571378
  7. ServiceNow Community, Query Business Rules vs ACL comparison (performance considerations): https://servicenow.com/community/developer-articles/query-business-rules-vs-acl-comparison/ta-p/2319460
  8. ServiceNow Community, BLOG: ServiceNow ACLs (Allow-If vs Deny-Unless): https://www.servicenow.com/community/itsm-forum/blog-servicenow-acls/td-p/3480783
  9. ServiceNow Community, How do I debug ACL? (impersonation and field ACL debugging): https://www.servicenow.com/community/developer-forum/how-do-i-debug-acl/m-p/3456217
  10. ServiceNow Community, Understanding ServiceNow ACL types and operations: https://servicenow.com/community/developer-forum/understanding-servicenow-acl-types-and-operations/td-p/3392074
  11. ServiceNow Community, ServiceNow Development Best Practices (Debug Security Rules, Stop Debugging): https://www.servicenow.com/community/developer-articles/servicenow-development-best-practices/ta-p/2312003
  12. Open Exam Prep, Access Control (ACLs) and Security (third-party study guide on evaluation order and the security_admin role): https://open-exam-prep.com/study-guides/servicenow-csa/database-security/acls-security
  13. Teachable course material, The Elements of an ACL (decision type introduced in Xanadu): https://tim-schliebs-s-school1.teachable.com/courses/2665107/lectures/57846720

Next in the series: continue with the roadmap topic after this one.

--

--