Permission values should be lowercase

Properties
FC0006 Info Style Code Fix Ignore Obsolete

In the Permissions property on codeunits, tables, pages, reports, request pages, xmlports, and queries, the casing of a permission letter has no runtime effect. The property does one job: when the object is on the call stack, it supplies the object side of the indirect-permission handshake. Before the object is allowed to read or write a table, the runtime checks two things:

  1. The user has at least indirect permission on the table, from a permission set or from the license.
  2. The object declares that operation in its Permissions property.

Neither check looks at whether the letter is M or m. A user without any permission on the table gets the same runtime error with RIMD as with rimd. A user with indirect permission succeeds with either. A user with direct permission never needed the property in the first place.

Uppercase invites a wrong conclusion. tabledata "G/L Entry" = RIMD on a codeunit reads as “this codeunit grants its callers direct write access to G/L entries”. It does not. Direct permission comes from a permission set or the license, and on license-restricted tables such as posted documents and ledger entries the license still wins:

The i means indirect insert. Capital I would mean direct — but on license-restricted tables, direct permissions in the Permissions property don’t actually elevate anything.

— Indirect Permissions in Business Central by Stefan Maroń

Use lowercase values so the declaration reads as what it is: an indirect-permission grant. The rule applies to every permission letter in the property, including the execute permission (x) on codeunit, page, and report entries.

Example

codeunit 50100 "Ledger Entry Mgt."
{
    Permissions = tabledata "G/L Entry" = RIMD, // Permission values should be lowercase [FC0006]
                  codeunit "Gen. Jnl.-Post Line" = X;
}

Use lowercase permission values instead:

codeunit 50100 "Ledger Entry Mgt."
{
    Permissions = tabledata "G/L Entry" = rimd,
                  codeunit "Gen. Jnl.-Post Line" = x;
}

What the Learn page says, and what the runtime does

The Permissions Property page on Microsoft Learn opens with a values table that assigns a meaning to the casing:

Specifies direct (R) or indirect (r) read permission.

— Permissions Property, property values on Microsoft Learn

That description is the source of most of the confusion around this property. The same page contradicts it twice further down. Its runtime results table has no direct-versus-indirect column on the property side, only set or not set:

Permissions granted by permission setPermission property not setPermission property set
NoneRuntime error caused by missing permissions.Runtime error caused by missing permissions.
Indirect permissionRuntime error caused by missing permissions.Success
Direct permissionSuccessSuccess

— Permissions Property on Microsoft Learn

And the page’s own worked example declares TableData "Cust. Ledger Entry" = rm in lowercase. The runtime results table describes the actual behavior; the values table at the top does not.

The base application is no better guide. Codeunit 80 “Sales-Post” declares everything in lowercase, while codeunit 408 “Dimension Management” mixes uppercase and lowercase across its entries. The mix is left over from the C/SIDE-to-AL conversion and years of edits by different teams. It does not encode a difference in behavior.

Permissions on pages

The property works the same way on a page as on a codeunit: it lets a user with indirect permission on a table reach that table through the page.

A user who has Indirect Read permission cannot open a page that displays data from a table unless the page has been given permission to read data from the table for the user.

— Permissions Property on Microsoft Learn

Declaring Permissions = tabledata "G/L Entry" = rimd on a page therefore hands every user who can open the page, and who holds at least indirect permission on G/L entries, a user-interface path to insert, modify, and delete those entries. That is a consequence of declaring the property on a page at all. Writing RIMD instead of rimd neither widens nor narrows it. When a page should only expose data the user can already access directly, leave the property off the page; use AccessByPermission when the goal is to hide a field or action from users who lack a permission.

Verifying the behavior

A Docker container running with a developer license does not enforce license-level indirect restrictions the way a cloud sandbox does. Code that passes locally can fail at a customer whose license only grants indirect access to posted documents and ledger entries. Verify permission scenarios in a cloud sandbox, or in an AL test codeunit with TestPermissions = Restrictive.

When a local test appears to show that one casing works and the other fails, something else changed between the two runs: the permission sets assigned to the test user, the license, or the object that was actually on the call stack. The runtime does not distinguish M from m in the Permissions property.

When the diagnostic is reported

  • The Permissions property is declared on a codeunit, table, page, report, requestpage, xmlport, or query object. Extension objects do not support the property.
  • Any entry in the property contains at least one uppercase letter. A single RIMD among lowercase entries, or a mixed value such as Rimd, is enough.
  • One diagnostic is reported per Permissions property, not per entry.

Code fix

The ALCops: Convert permission values to lowercase code fix converts every permission value in the Permissions property to lowercase, preserving entry order and formatting.

The AC0031 code fix, which adds missing tabledata entries to the property, already emits lowercase values. Tools that generate the property in uppercase, such as the Add permissions to all tables used by this object command in AZ AL Dev Tools, produce a Permissions property that this rule flags; running the code fix once normalizes it.

Exception

The rule does not apply to permissionset and permissionsetextension objects. There the casing is semantic: uppercase grants direct permission, lowercase grants indirect permission, and changing one to the other changes what users can do.

permissionset 50100 "Sales Documents"
{
    Assignable = true;

    // Uppercase is correct here: it grants direct permission
    Permissions = tabledata "Sales Header" = RIMD;
}

The InherentPermissions property and the [InherentPermissions] attribute are also excluded for the same reason: there uppercase and lowercase declare direct and indirect permission respectively.

The AccessByPermission property is not covered either. Although it uses the same syntax as Permissions, it is a UI-visibility mask: it hides a table field, page field, page part, action, or object from users who lack the listed permission. It grants nothing, so it has no direct/indirect distinction, and Microsoft Learn documents the uppercase form as its canonical spelling.

page 50100 "Sales Order Extras"
{
    actions
    {
        area(Processing)
        {
            action("BOM Level")
            {
                AccessByPermission = tabledata "BOM Buffer" = R;
            }
        }
    }
}

See also