Recently, we worked on a client requirement where they wanted to mask and unmask the “Account Number” column in the Account entity based on Teams. For example, show unmasked data to the Admin team and masked data to the Sales team on the Account form. This way, the Admin team can see the actual account number in plain text, while the Sales team sees a masked version.
To achieve this, we leveraged one of the latest features in Microsoft Dataverse (Dynamics 365 CRM): Field Masking
Assumptions:
- The Admin and Sales teams are already created and available in the system.
- You have appropriate permissions to configure column security, and register plugins
High-Level Steps to Implement Field Masking:
- Create a Masking Rule.
- Enable Column Security and assign the masking rule to the field.
- Create two Column Security Profiles: one for the Admin team and one for the Sales team.
- Grant Read Unmasked permission to the Admin team.
- Deny Read Unmasked permission to the Sales team.
- Create a plugin on the Retrieve message to programmatically fetch unmasked data and show it on Account FORM.
Detailed Steps
Step 1: Create a Masking Rule
- Navigate to Solutions via make.powerapps.com.
- Create a new masking rule. For example, if your Account Number format is
S1234567z
, you can mask it to#567z
.




Step 2: Enable Column Security and Apply the Masking Rule
- Go to Accounts → Columns → Select Account Number.
- Enable Column Security.
- Assign the masking rule created in Step 1.


Step 3: Create Column Security Profiles
🔹 Admin Team Profile
- Create a profile for the Admin team.
- Set the following permissions on the “Account Number” field:
Read | Allowed |
Read Unmasked | All Records |
Update | Allowed |
Create | Allowed |
Refer screenshots:




Add ADMIN team in this Profile:


Sales Team Profile
- Create a profile for the Sales team.
- Set the following permissions on the “Account Number” field:
Read | Allowed |
Read Unmasked | Not Allowed |
Update | Allowed |
Create | Allowed |
Refer screenshots:


Add Sales team to this Column Security Profile:


Step 4: Access Unmasked Data via OData (Optional)
This step 4 is optional, just to retrieve unmasked data programmatically using the Web API, use the UnMaskedData=true
parameter:
https:/
/<yourorg>.crm.dynamics.com/api/data/v9.
1/accounts(<
GUID>)
?UnMaskedData=
true
Replace <GUID>
with the Account record ID.
For example: https://org87atest5.crm.dynamics.com/api/data/v9.1/accounts(a62169f9-1449-f011-877b-6045bdad9aaf)?UnMaskedData=true


Step 5: Plugin to Show Unmasked Data on Account Form
To display unmasked data directly on the Account form when an Admin opens the record, create a plugin on the Retrieve
message.
Register the Plugin step:


Plugin Code Snippet:
Below code snippet is used to retrieve the Unmasked Data by setting property “UnMaskedData” from the Plugin.


You can see the Unmasked value for ADMIN team as below screenshot:


Refer the Full Plugin Code on Retrieve Message:
<code>
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using System;
namespace PLugin
{
public class ReadUnMaskData : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
// Check if the plugin is triggered by a “Create” message
if (context.MessageName != “Retrieve”) return;
// Obtain the organization service
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
// Obtain tracing service for logging
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
try
{
// Log plugin execution
tracingService.Trace(“ReadUnMaskData started.”);
if (context.Depth > 1)
return;
string unMaskedAccountNumber = string.Empty;
// Get the output parameters and entity
if (context.OutputParameters.Contains(“BusinessEntity”) && context.OutputParameters[“BusinessEntity”] is Entity entity)
{
tracingService.Trace(“Inside context.OutputParameters.Contains(Entity)”);
var request = new RetrieveRequest
{
Target = new EntityReference(“account”, entity.Id),
ColumnSet = new ColumnSet(“accountnumber”)
};
request[“UnMaskedData”] = true;
// Execute the request
var response = (RetrieveResponse)service.Execute(request);
if (response != null && response.Entity.Contains(“accountnumber”) && response.Entity.Attributes[“accountnumber”] != null)
{
tracingService.Trace(“inside response.Entity.Contains(accountnumber)”);
unMaskedAccountNumber = (string)response.Entity.Attributes[“accountnumber”];
tracingService.Trace(“unmasked values:: ” + unMaskedAccountNumber);
if (entity.Attributes.Contains(“accountnumber”))
{
tracingService.Trace(“inside entity.Attributes.Contains(accountnumber)”);
entity.Attributes[“accountnumber”] = unMaskedAccountNumber;
}
}
}
}
catch (Exception ex)
{
tracingService.Trace($”Error in ReadUnMaskData: {ex.Message}”);
throw new InvalidPluginExecutionException($”Error occurred: {ex.Message}”, ex);
}
}
}
}
</code>
Finally, with the help of above steps:
- The Admin team will see the unmasked account number on the form.
- The Sales team will see the masked value according to the defined rule.
- Programmatic access to unmasked values is controlled via column security and plugins