In this video, I’m going to, once and for all, explain what the weird “Indirect” permissions do and how you can work with it. Check it out:

In this video, Erik explains one of the most common sources of confusion when working with permissions in Business Central: indirect permissions. If you’ve ever built an API or extension, given it all the permissions you thought it needed, and still received a permission error — indirect permissions are likely the culprit. Erik walks through what indirect permissions actually mean, how they work, and how to properly use them in your AL code.
The Problem: Everything Looks Right, But Permissions Fail
Erik starts by navigating to the effective permissions for a user in a cloud sandbox. At first glance, everything looks fine — lots of “Yes” entries across the board. But then things get interesting:
- G/L Entry: Read permission is “Yes”, but Insert, Modify, and Delete are all “Indirect”
- Customer Ledger Entries: Insert and Delete are “Indirect”, but Modify is “Yes”
- Vendor Ledger Entries: Similar pattern of indirect permissions
- Sales Shipment Header: You can delete them but cannot modify them
- Posted Purchase documents: Headers have different permissions than lines
Some of these patterns may seem inconsistent, but most of them exist because of local accounting laws — you’re generally not allowed to mess with posted data directly.
Demonstrating the Error
To show what happens when you try to directly modify a table protected by indirect permissions, Erik writes a simple piece of code that attempts to modify a G/L Entry’s description:
GLEntry.FindFirst();
GLEntry.Description := 'Look Mom, I''m coding AL!';
GLEntry.Modify();
When deployed to a cloud sandbox (which uses an end-user license), this immediately fails with the error:
“The current permission prevented the action — table data G/L Entry modify — indirect permissions”
Erik points out an important gotcha: if you run this same code on a local Docker container with a developer license, it works just fine. This is dangerous because you might think your code is correct during development, only to have it fail when deployed to a customer environment. Always test with realistic license conditions.
What Does “Indirect” Actually Mean?
Indirect permission means that you do have permission to perform the action, but only when it’s done through an object that itself has been granted explicit permission to that table. Think of it as a handshake — two pieces need to come together:
- The user must have at least indirect permission on the table
- The object (typically a codeunit) must declare explicit permission to that table via the
Permissionsproperty
If the user has no modify access (not even indirect), then the object’s permissions alone won’t be enough. And if the user already has direct (“Yes”) access, the object’s permission declaration doesn’t add anything further.
The Solution: Using the Permissions Property on a Codeunit
The fix is to move your table-modifying logic into a codeunit and declare the necessary permissions on that codeunit using the Permissions property. Here’s the complete working example:
pageextension 50100 CustomerListExt extends "Customer List"
{
trigger OnOpenPage();
var
Stuff: Codeunit "Manipulate GL";
begin
Stuff.DoX();
end;
}
codeunit 50100 "Manipulate GL"
{
Permissions = tabledata "G/L Entry" = M;
procedure DoX()
var
GLEntry: Record "G/L Entry";
begin
GLEntry.FindFirst();
GLEntry.Description := 'Look Mom, I''m coding AL!';
GLEntry.Modify();
end;
}
The key line is Permissions = tabledata "G/L Entry" = M; on the codeunit. The M stands for Modify. This declaration, combined with the user’s indirect modify permission on G/L Entry, satisfies both halves of the handshake — and the code runs successfully.
Erik verifies this by deploying to the cloud sandbox again and checking the G/L Entries page, where the description “Look Mom, I’m coding AL!” appears on the modified entry.
How the Base App Uses This Pattern
This isn’t a niche technique — it’s exactly how the standard Business Central base app works. Erik points to General Journal – Post Line (codeunit 12) as a prime example. The first section of that codeunit is a large block of permission declarations covering all the posted tables it needs to write to — bank account ledger entries, G/L entries, and many more.
The posting routine can write to those protected tables precisely because users have indirect permissions and the codeunit declares the matching direct permissions.
A Word of Caution
Erik closes with an important warning: just because you can add permissions to modify posted data doesn’t mean you should. Before you add Permissions = tabledata "Some Posted Table" = M; to your codeunit, ask yourself:
- Is this necessary? Think twice about whether you truly need to modify posted data.
- Is there an existing codeunit you can call? The base app may already have a proper code path that handles the modification correctly, avoiding an extra entry point for modifying protected data.
- Have you informed the stakeholders? Depending on your jurisdiction, the CFO or client needs to know that your extension can modify posted entries. This may need to be included in the yearly audit report to keep the system compliant with local accounting laws.
Bonus Tip: Debugging AL Exceptions
During the demo, Erik showed a handy debugging trick. When you hit a breakpoint on a permission error, you can type AL Exception in the debug console’s watch or evaluation window to see the full exception message — a useful technique for diagnosing permission issues and other runtime errors.
Summary
Indirect permissions are Business Central’s way of protecting sensitive tables (especially posted data) while still allowing controlled, programmatic access. The mechanism requires two things working together: the user must have indirect permission, and the executing object must declare explicit permission via the Permissions property. This pattern is used extensively throughout the base app, particularly in posting routines. While it’s straightforward to implement in your own AL extensions, always consider the broader implications — modifying posted data has real-world consequences that extend well beyond your code.