Acumatica · Customization

Acumatica GI Row-Level Security — Patterns That Work

How to make Acumatica Generic Inquiries respect row-level security — branch restrictions, customer-specific data, role-based filtering, and the tests that catch leaks before users do.

John Kihiu12 min read

Generic Inquiries are the most-shared artifacts in any Acumatica instance — everyone can see them on a dashboard, everyone exports them to Excel — which makes them the most common place data leaks between people who should not see each other's numbers. Salespeople seeing other reps' commissions, branch staff seeing group-wide margins, a warehouse GI exposing cost columns to floor staff. This post is the set of row-level security patterns I actually use, in the order I reach for them.

Start with the security the platform already enforces

A GI runs under the current user's context, so three platform mechanisms apply automatically before you write any conditions:

The pattern that works is: lean on branch security and restriction groups for the entities that support them, and only hand-roll conditions for the gaps.

Pattern 1: conditions on the current user

For "each salesperson sees their own orders" style requirements, the GI Conditions tab can reference the session. The two expressions I use constantly:

GI CONDITIONS
-- current user's ID (Users.PKID / Guid)
SOOrder.OwnerID   Equals   =[AccessInfo.UserID]

-- current user's linked employee / contact
SOOrder.SalesPersonID  Equals  =[EPEmployee.SalesPersonID]
   (with EPEmployee joined on DefContactID = AccessInfo.ContactID)

The gotcha: managers. The moment one person needs to see their team's rows, a flat equality condition collapses into a maintenance headache of per-user GIs. Resolve that with the workgroup tree instead — join EPCompanyTreeMember and filter on membership of the current user's workgroup and its children. It is more joins, but it means org changes are handled on the Company Tree screen by an admin, not by you editing GI conditions.

Pattern 2: restriction groups where they exist

If the requirement maps onto an entity Acumatica already supports in Row-Level Security — GL accounts, subaccounts, customers, vendors, inventory items, warehouses — use restriction groups even if it feels heavier up front. A group named SALES-KE containing the Kenyan customers and the Kenyan sales role gives you enforcement in every GI, report, and screen simultaneously. A GI condition protects one GI; someone will eventually clone the GI and forget the condition. Restriction groups fail closed.

A/B/A-inverse group types matter

Acumatica's restriction group types (A, B, and their inverse variants) control whether membership grants or revokes visibility and how ungrouped entities behave. Type A with inverse semantics is the one that catches people: an entity in no group is visible to everyone. If your requirement is "deny by default", you must put every sensitive entity into at least one group. Audit this with a GI over the group tables themselves.

Pattern 3: a secured projection DAC

When conditions get too gnarly — say, visibility rules that depend on a custom mapping table — I stop fighting the GI designer and push the security into a PXProjection in a customization project, then build the GI over the projection:

C#
[PXProjection(typeof(
    SelectFrom<SOOrder>
      .InnerJoin<KGRepTerritory>
        .On<KGRepTerritory.salesPersonID.IsEqual<SOOrder.salesPersonID>>
      .Where<KGRepTerritory.userID.IsEqual<AccessInfo.userID.FromCurrent>>),
    Persistent = false)]
[PXCacheName("Orders (Territory Secured)")]
public class SecuredOrder : PXBqlTable, IBqlTable
{
    [PXDBString(2, IsKey = true, BqlField = typeof(SOOrder.orderType))]
    public string OrderType { get; set; }
    public abstract class orderType : BqlString.Field<orderType> { }

    [PXDBString(15, IsKey = true, BqlField = typeof(SOOrder.orderNbr))]
    public string OrderNbr { get; set; }
    public abstract class orderNbr : BqlString.Field<orderNbr> { }
    // ... only the fields the GI is allowed to expose
}

Two benefits: the Where clause travels with the DAC, so every GI built on it inherits the restriction; and the projection only surfaces the fields you declared, so nobody can add the cost column back in the GI designer because it simply is not there. That second property is underrated — it is field-level security by omission.

Anti-patterns I keep removing from client instances

Wrapping up

Row-level security in GIs is layered: platform branch security and restriction groups first, session-scoped conditions for ownership rules, and a secured PXProjection when the logic outgrows the designer. Whichever layer you use, test as a real restricted user, and re-test after every GI edit — the designer makes it very easy to add a join that silently widens the result set.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.