4 min readRishi

Extensible Data Security (XDS) in D365 Finance & Operations: Row-Level Security That Scales

Role-based security in Dynamics 365 Finance & Operations controls which objects a user can reach. It does not, on its own, control which rows. When a sales rep should only see their own region's customers, or a subcontractor should see only their own projects, you need Extensible Data Security (XDS) — policies that inject a WHERE clause into every query against a table, automatically and unavoidably. Here is how to build one that is correct and fast.

How XDS works

An XDS policy has three ingredients:

  • A constrained table — the table whose rows you want to filter (for example CustTable).
  • A primary table / policy query — a view that returns the rows the current user is allowed to see, based on their context.
  • A context — what "the current user" means: a role, or an application-defined value you set in code.

At runtime, the kernel rewrites every query against the constrained table to join your policy query. The user literally cannot fetch rows the policy excludes — not through forms, not through OData, not through a report. That is the strength of XDS: it is enforced at the data layer, not bolted onto the UI.

A worked example: rep sees only their territory

Suppose CustTable has a SalesResponsible field, and you want each rep to see only customers they own.

  1. Create a query (MyCustTerritoryPolicyQuery) over CustTable that ranges SalesResponsible to the current worker.
  2. Create a security policy referencing that query, with CustTable as the constrained table and ContextType = RoleName, tied to the "Sales rep" role.
  3. The policy query uses a context function to resolve the current user to a worker:
-- conceptual shape of the policy query range
SELECT * FROM CustTable
WHERE CustTable.SalesResponsible =
      (SELECT PersonnelNumber FROM HcmWorker
       WHERE HcmWorker.Person = currentUserWorkerPerson())

In X++ the range is expressed with a computed column or a method on the policy that returns the constraint. The kernel appends it to every consuming query.

Application context for dynamic filters

Role-based context is static. When the filter depends on runtime data — "show only the company the user picked in a lookup" — use application context. You set a value in code and the policy reads it:

// Set the context for the session
XDSServices xds = new XDSServices();
xds.setContext('MyRegionContext', strFmt('%1', selectedRegionId));

Your policy's context type is then ContextString, and the policy query filters on that value. Application context is powerful but stateful — set it deliberately at the start of the relevant operation and clear it when done, or you will leak one user's filter into another scenario.

Performance: the part that bites

XDS adds a join to every query on the constrained table. If your policy query is expensive, you have just made the entire system slow.

PitfallFix
Policy query joins many tablesFlatten to a single indexed lookup; precompute if needed
Constraint column not indexedAdd an index covering the filter field
Policy applied to a hot, high-volume tableConstrain a narrower table or use a covering view
Context resolved with heavy code each callCache the resolved value per session

The golden rule: the policy query must be cheap and indexable, because it runs implicitly on reads you never see in your own code.

Operational tips

  • Scope tightly. Apply XDS to the smallest set of tables that achieves the requirement. Every constrained table is a permanent tax on queries.
  • Mind integrations. OData and data entities honor XDS. An integration account that needs to see everything should not be in the constrained role — or you will spend a day debugging "missing" rows that the policy is hiding by design.
  • Test the negative case. Confirm a user cannot see excluded rows through OData and exports, not just the form. XDS is enforced everywhere, which is exactly why you must verify everywhere.
  • Document the context. Six months later, "why can't finance see these vouchers?" has one answer hidden in a policy. Keep a short register of active XDS policies and what each constrains.

XDS is the right tool when row visibility is a security requirement rather than a UI nicety. Build the policy query like it is on the hot path — because it is.

Keep reading

Newsletter

New posts, straight to your inbox

One email per post. No spam, no tracking pixels, unsubscribe anytime.

Comments

No comments yet. Be the first.