Dataverse security goes beyond a simple on/off switch. Two mechanisms work together to control what data each user can access, and they operate at different granularities:
- Security Roles determine which rows a user can see and what they can do with them.
- Column Security Profiles determine which fields within those rows are readable or editable.
The key thing to understand upfront: these two layers are evaluated independently. A user can have access to a row and still have certain columns blanked out. Getting one right does not automatically handle the other.
The Scenario
The examples across this series use a fictional company called Contoso with three departments: HR, Finance, and Engineering. Contoso is rolling out a Power Platform solution to manage employee salary data, and the security configuration needs to reflect real organisational boundaries — HR staff can see HR records, Finance can see Finance records, and sensitive columns like Salary are restricted to those who genuinely need them.
Four people appear throughout the series. They are introduced here so nothing feels retrofitted when later posts add complexity:
Alex is the Power Platform admin. Alex does all the configuration — creating tables, setting up security roles, managing column security profiles, and organising users into teams. Alex has the System Administrator role in the environment.
Jordan is an HR employee. In this post, Jordan is the test subject for individual row and column access. Jordan has no admin rights.
Morgan is Jordan’s manager and the HR team lead. Morgan appears in Part 2, where Organisation-level read access and team-based profile assignment are introduced.
Sam is a Finance employee, also introduced in Part 2, to demonstrate how Business Units keep department data separate.
To follow along with this post you only need Alex and Jordan set up as users. Part 2 covers adding Morgan and Sam, along with the full user and team management workflow in the Admin Center.
How to view the environment as Jordan: open an in-private or incognito browser window, navigate to make.powerapps.com, and sign in with Jordan’s credentials. Keep both windows open side by side — Alex’s window for configuration, Jordan’s window for verification.
The Test Table
Before seeding data, Alex needs to create the table and assign Jordan the minimum permissions to access the environment. Without a security role, Jordan cannot log in to the maker portal at all — which means Jordan cannot create the test row that row-level security depends on.
Create the Table
In the maker portal, create a table called Employee Salary with the following schema:
| Column | Type |
|---|---|
| Employee Name | Text (Primary column) |
| Department | Choice — HR, Finance, Engineering |
| Salary | Currency |
| National ID | Text |
| Hire Date | Date Only |
Assign Jordan a Security Role
Before Jordan can do anything in the environment, Alex needs to set up the security role Jordan will use throughout this post.
Go to admin.powerplatform.microsoft.com → your environment → Settings → Users + permissions → Security roles → + New role.
Name the role Employee Self-Service. Find the Employee Salary table in the list and set the Read privilege to User. Save and close.
Now assign this role to Jordan. Go to Users + permissions → Users → find Jordan → Manage security roles → tick Employee Self-Service. Also tick the built-in Basic User role — without it, Jordan cannot access the environment at all, regardless of any custom roles assigned to them.
Seed the Test Data
Once the table exists and Jordan has a role, add a few rows. Here is the important part: Alex should create one row, and Jordan should create another. The Owner field on each row is set automatically to whoever creates it — and that ownership is exactly what row-level security acts on. If all rows are owned by Alex, you will not be able to verify that Jordan’s access is correctly scoped to their own data.
Once both rows exist, you are ready to verify security behaviour.
Row-Level Security
Row access is controlled through the access level set on each privilege inside a Security Role. The access level defines the scope of rows that privilege applies to:
| Access Level | What the user sees |
|---|---|
| User | Only rows they own (or rows shared with them) |
| Business Unit | Rows owned by anyone in their Business Unit |
| Organisation | All rows in the environment |
This is not a global setting — it is per-table, per-privilege. A user can have Organisation-level Read on one table and User-level Read on another, all within the same role.
What to Expect
Switch to Jordan’s browser window. Open the Employee Salary table. Jordan will see only the row they own — the one they created earlier. Alex’s row is completely invisible. It does not appear in views, search results, or API queries. Jordan is not seeing a filtered-down version of the data; from their perspective, those other rows simply do not exist.
If Jordan can see Alex’s row, double-check that the Read privilege on the Employee Self-Service role is set to User and not Organisation.
Column-Level Security
Even on a row Jordan can see, individual columns can be restricted independently. This is done through Column Security Profiles — a separate construct from Security Roles, configured in a different place, evaluated by Dataverse as a separate gate.
The mechanism works in three steps: enable security on the column, create a profile that grants access to that column, then assign users or teams to that profile. Anyone not in a profile with access to a secured column sees that column as blank.
Enable Security on the Column
Alex does this in the maker portal.
Go to Tables → Employee Salary → open the Salary column → expand Advanced options → toggle Enable column security to On → Save.
Do the same for National ID if you want to restrict that column too.
As soon as column security is enabled on a column, it goes blank for everyone — including Alex, even though Alex has the System Customizer role. The only role that bypasses column security unconditionally is System Administrator. This is why it matters to test with Jordan’s account rather than Alex’s — Alex will always see the data, which makes verification meaningless.
Create a Column Security Profile
Back in the Admin Center, Alex goes to Settings → Users + permissions → Column security profiles → + New profile.
Name it HR Salary Access. Open the profile, go to the Column permissions tab, find Salary, and set Read, Update, and Create to Allowed. Save.
Then go to the Users tab and add Alex. Do not add Jordan yet — leaving Jordan out is what lets you verify the restriction is working.
What to Expect
Switch to Jordan’s browser window and refresh. Jordan’s own row appears, the other columns are populated, but Salary is blank. Jordan can see that the row exists and can read everything else — just not the Salary value.
Now go back to Alex’s Admin Center window, open the HR Salary Access profile, go to the Users tab, and add Jordan. Sign Jordan out and back in (profile changes can take a minute to propagate — if the column is still blank after signing back in, wait 60 seconds and try again).
Refresh Jordan’s view. Salary is now visible.
How the Two Layers Interact
Jordan in this example passes the row check — it is their row — but fails the column check because no profile grants them access to Salary. The column gate is evaluated regardless of the row outcome. Both must pass for a value to be visible.
Morgan, who appears in Part 2 with an Organisation-level role and a team-based profile assignment, passes both gates and sees the Salary value on every row.
Things Worth Knowing
System Administrators bypass column security. Alex will always see all column data regardless of profiles. Always verify with Jordan’s account, not Alex’s.
Basic User is non-negotiable. Jordan needs Basic User in addition to Employee Self-Service. Without it, Jordan hits an access error before any table-level permissions are even evaluated.
Profile changes can take a minute to propagate. If Salary is still blank after adding Jordan to the profile, sign Jordan out and back in before concluding something is wrong.
Assigning profiles directly to users does not scale. Right now Jordan is assigned to the HR Salary Access profile individually. When Morgan joins and needs the same access, Alex has to add them manually. When a third HR employee joins, same thing. Part 2 replaces this with team-based assignment — one team, one profile, and every new member automatically inherits the right access.