Security Policies (XDS)
A Security Policy (AxSecurityPolicy), also known as Extensible Data Security (XDS), implements row-level security in D365 F&O. While roles, duties, and privileges control what operations a user can perform, security policies control which records they can see and modify.
Security policies work by injecting additional WHERE clause conditions into every query that accesses the constrained tables. This filtering happens transparently at the kernel level — application code does not need to be aware of it.
How XDS Works
- A query defines the filtering logic — it specifies which records from the primary table should be visible based on the user's context.
- The policy identifies a primary table and optional constrained tables (related tables that are also filtered).
- The policy is bound to a context — either a specific role, a role property, or a context string — that determines when the policy is active.
- At runtime, when the user's security context matches the policy's context, the query is automatically applied as an EXISTS JOIN (or NOT EXISTS JOIN) to every data access on the constrained tables.
Context Types
The ContextType property determines when the policy is active:
| Context Type | Value | Description |
|---|---|---|
| ContextString | 0 | The policy is active when application code explicitly sets the XDS context to a matching string via XDSServices::setXDSContext(). |
| RoleName | 1 | The policy is active whenever the current user is assigned to the role specified in the RoleName property. |
| RoleProperty | 2 | The policy is active based on a property defined on the security role's ContextString. |
RoleName Context
The most common scenario — the policy automatically applies whenever a user has a specific role:
ContextString Context
Programmatic control — your code activates the policy by setting a context string:
When using ContextString-based policies, always reset the context to an empty string when your operation completes. Failing to do so leaves the filter active for subsequent operations in the same session, which can cause unexpected data visibility.
Constrained Tables
A policy has a primary table (the first data source in the query) and optionally additional constrained tables. Each constrained table is an AxSecurityPolicyConstrainedEntity:
| Property | Type | Description |
|---|---|---|
| Name | String | The AOT name of the constrained table. |
| Constrained | NoYes | Whether the policy filtering is applied to this table. |
| Tags | String | Tags for this element separated by semicolon. |
Constrained tables can themselves contain nested ConstrainedTables children, forming a hierarchy. When the policy's ConstrainedTable property is set to Yes, the primary table itself is also constrained.
Join Behaviour
The UseNotExistJoin property controls how the policy query integrates with the user's data access:
| Value | Behaviour |
|---|---|
| No (default) | Uses an EXISTS JOIN — only records matching the policy query are returned (whitelist). |
| Yes | Uses a NOT EXISTS JOIN — records matching the policy query are excluded (blacklist). |
Operations
The Operation property controls which data operations the policy filters:
| Value | Applies To |
|---|---|
| Select (0) | Read operations only. |
| Insert (1) | Insert operations only. |
| Update (2) | Update operations only. |
| Delete (3) | Delete operations only. |
| InsertUpdateDelete (4) | All write operations (insert, update, delete). |
| AllOperations (5) | All operations including reads and writes. |
Creating a Security Policy
- Create a query that defines the row-level filter logic. The query's first data source is the primary table.
- In Visual Studio, add a new Security Policy item to your project.
- Set the
Queryproperty to your query. - Set the
PrimaryTableto the first data source table in the query. - Set the
ContextTypeand eitherRoleNameorContextString. - Set the
Operationto indicate which operations are filtered. - Add constrained tables if the filter should apply to related tables beyond the primary table.
- Set
EnabledtoYes. - Build and deploy.
Example — Company-Level Data Filtering
A common pattern is restricting users to records belonging to their assigned legal entity:
Properties
| Property | Display Name | Type | Description |
|---|---|---|---|
| Security PolicyAxSecurityPolicy | |||
| Name | Name | String | The name of the element. |
| IsObsolete | Is Obsolete | NoYes | Determines whether the element is deprecated or not. Values: No (0), Yes (1) |
| Visibility | Visibility | CompilerVisibility | The visibility of the element. Values: Private (0), Protected (1), Public (2), Internal (3), InternalProtected (4) |
| Tags | Tags | String | Tags for this element separated by semicolon. |
| Label | Label | String | Display name for the policy. |
| HelpText | Help Text | String | Help text for the policy. |
| Enabled | Enabled | NoYes | Indicates whether the policy has been enabled. Values: No (0), Yes (1) |
| PrimaryTable | Primary Table | String | The first data source in the query for the policy. |
| Query | Query | String | Query that is the basis for the policy. |
| ConstrainedTable | Constrained Table | NoYes | Indicates whether the policy restricts the data values in records returned from the primary table. Values: No (0), Yes (1) |
| Operation | Operation | SecurityPolicyApplicability | Which data operations the policy filters. Values: Select (0), Insert (1), Update (2), Delete (3), InsertUpdateDelete (4), AllOperations (5) |
| ContextType | Context Type | SecurityPolicyContextType | How the policy determines applicability. Values: ContextString (0), RoleName (1), RoleProperty (2) |
| ContextString | Context String | String | If the context type is ContextString, this property displays the string. |
| RoleName | Role Name | String | If the context type is RoleName, this property displays the AOT name of the role. |
| UseNotExistJoin | Use Not Exist Join | NoYes | Whether the security query is applied as a NOT EXISTS JOIN (blacklist) instead of an EXISTS JOIN (whitelist). Values: No (0), Yes (1) |
| Constrained EntityAxSecurityPolicyConstrainedEntity | |||
| Name | Name | String | The AOT name of the constrained table. |
| Constrained | Constrained | NoYes | Indicates whether the policy filtering is applied to this table. Values: No (0), Yes (1) |
| Tags | Tags | String | Tags for this element separated by semicolon. |