Most Power Platform developers know how to create a parent-child lookup — far fewer understand what it takes to make Dataverse actually treat that relationship as a hierarchy. This deep-dive lesson covers hierarchy designation, the `Under` query operator, manager and position hierarchy security, and the hierarchy visualizer control, with precise guidance on where each feature's boundaries lie.

Imagine you're building a field service management application for a company that operates across six regional divisions, each with district managers overseeing multiple teams, each team managing dozens of work orders, and every work order potentially spawning child tasks. Your data model has to represent that organizational reality faithfully — not just as a flat list of records with lookup fields, but as a true tree structure where the system understands ancestry and descent. Now add the security requirement: a regional director should automatically see every record beneath them in the hierarchy without you manually assigning permissions to every row. That's the problem hierarchical relationships in Dataverse were designed to solve.
Most Power Platform developers understand one-to-many relationships and lookup columns. Far fewer understand the distinction between a standard self-referential lookup and a designated hierarchy relationship, the implications that designation has on query behavior, what hierarchical security actually does versus what people assume it does, and how the hierarchy visualizer control in model-driven apps surfaces all of this to end users. The gaps in this knowledge lead to subtle security holes, confusing UX, and rollup calculations that don't behave the way you'd expect.
By the end of this lesson, you'll be able to design and implement true hierarchical table structures in Dataverse, configure the hierarchical security model so that record access propagates correctly up and down the tree, use Dataverse queries that leverage hierarchy traversal, build hierarchy visualizations that make the tree navigable for users, and reason clearly about the architectural trade-offs involved in each decision.
What you'll learn:
Under and EqualUserOrUserHierarchy operators in FetchXML and the Web APIThis lesson assumes you are comfortable with Dataverse fundamentals — tables, columns, and the relationship system. You should understand how one-to-many relationships work and how cascade behaviors affect related records. If you need to solidify that foundation first, read through Configuring Dataverse Table Relationships in Model-Driven Apps: One-to-Many, Many-to-Many, and Cascade Behaviors Explained before continuing.
You should also have basic familiarity with Dataverse security roles and business units. The Dataverse Security: Business Units, Security Roles, and Teams lesson covers those foundations.
Before touching any configuration, you need to understand what Dataverse means by hierarchy — because it's more specific than a generic parent-child relationship.
In Dataverse, any table can have a self-referential one-to-many relationship. That means the Account table, for example, has a lookup back to Account — one account can be the parent of many child accounts. This is technically a parent-child relationship, and it exists on the Account table by default (the parentaccountid column). But a lookup column on its own doesn't give Dataverse any special awareness that a hierarchy exists.
The critical step is designating a specific self-referential relationship as the hierarchy relationship for that table. A table can have many self-referential lookups, but only one can be the designated hierarchy at any time. Once designated, that relationship unlocks:
Under, Not Under, EqualUserOrUserHierarchyAndTeams, and related conditions that traverse the treeKey insight
Designating a hierarchy relationship doesn't change the underlying data storage. The records still store a simple GUID in the lookup column. The "hierarchy awareness" is a metadata flag that instructs the Dataverse query engine and security engine to treat that relationship as a tree when evaluating certain operations. This means switching the designated relationship mid-deployment has no effect on existing data — but it does immediately affect security and query behavior.
Dataverse hierarchy designation only works on self-referential relationships — a lookup from a table back to itself. You cannot designate a relationship between two different tables (e.g., Region → District → Territory) as a Dataverse hierarchy in the sense that unlocks hierarchy security and the visualizer.
For multi-table hierarchies, you need a different architectural approach: either flatten your hierarchy into a single self-referential table (using a "type" or "level" column to distinguish levels), or accept that you'll handle the cross-table aggregation logic yourself. Both approaches are valid, and we'll cover the trade-offs in detail later.
The self-referential constraint catches people off guard, especially those coming from SQL backgrounds where hierarchical CTEs work across any columns. In Dataverse, the hierarchy engine is specifically designed around the single-table tree pattern.
Let's build a concrete example. Suppose you're modeling an organizational unit hierarchy for a professional services firm: each Organizational Unit record can have a parent Organizational Unit, creating a tree from the company root down to individual teams.
Navigate to make.powerapps.com, open your solution, select your table (we'll call it crf_organizationalunit), and go to the Relationships section.
Select Add relationship, then Many-to-one. In the "Related table" dropdown, select the same table — Organizational Unit. Give the relationship a meaningful schema name, such as crf_organizationalunit_parentunit. The lookup column this creates will store the GUID of the parent record.
Set the relationship behavior. For a hierarchy, you typically want:
Warning
If you set Delete to Cascade on a hierarchy relationship, deleting any node will recursively delete its entire subtree. In a live system, this is almost always catastrophic. Use Restrict until you have very specific requirements otherwise, and even then, handle deletion through custom logic that validates the subtree state first.
Save the relationship.
After the relationship is created, find it in the relationships list. Select it to open the relationship editor. You'll see a checkbox or toggle labeled Hierarchical (in the modern maker portal it appears as "Set as hierarchical"). Enable it and save.
You can also do this through the classic solution explorer: navigate to the table, open Relationships, find your self-referential relationship, double-click it, and check the Hierarchical checkbox.
Once designated, Dataverse updates the EntityMetadata for the table. If you retrieve the table's metadata via the Web API, you'll see hierarchyrelationshipname populated with your relationship's schema name.
The lookup column created by the relationship (e.g., crf_parentunitid) needs to appear on the Organizational Unit form so users can assign parents. Navigate to your table's main form.
When placing this lookup on the form, consider two things. First, add filtering to prevent circular references in the UI — a record should not be able to select itself or its own descendants as its parent. You can apply a view filter on the lookup, though this doesn't prevent circular references at the data layer (that requires a plugin or business rule). Second, consider making the field optional (not business required) to accommodate root-level units that have no parent.
Tip
Prevent circular reference loops with a synchronous plugin on the Pre-Create and Pre-Update messages of your table. Walk up the proposed parent chain using the Web API and throw an InvalidPluginExecutionException if you encounter the current record's GUID. This is defense-in-depth that UI-layer filters alone can't provide.
For more on form design patterns, see Designing Model-Driven Forms: Sections, Tabs, Subgrids, and Quick View Forms for guidance on placing and organizing lookup fields effectively.
Hierarchical security is one of the most misunderstood features in the Power Platform security model. Let's establish the mental model correctly before diving into configuration.
Hierarchical security in Dataverse comes in two distinct flavors:
Manager hierarchy — based on the systemuser table's built-in parentsystemuserid self-referential relationship. Users who are managers of other users (as expressed by this relationship) gain access to records owned by users beneath them in the manager chain.
Position hierarchy — based on a separate Position table, where users are assigned to positions and positions form their own tree. A user at a higher position gains access to records owned by users at lower positions.
Both flavors apply only to the systemuser hierarchy — that is, they control which user's owned records another user can see. They do not apply to arbitrary table hierarchies you create.
Key insight
This trips up almost everyone the first time. Hierarchical security does not mean "a parent Organizational Unit record can see all child Organizational Unit records based on the hierarchy relationship." It means "a user who is above another user in the manager or position hierarchy can see records owned by that lower user." The hierarchy security model is about the user-ownership chain, not the data structure chain.
To enable manager-based hierarchical security:
The systemuser manager chain is configured through the Manager field on the User record (the parentsystemuserid column). Setting this field connects users into a tree.
Warning
The depth setting applies globally to the entire hierarchy security configuration. You cannot set depth=2 for one security role and depth=5 for another. If you need variable depth access, you'll need to combine hierarchy security with other mechanisms — explicit team membership or record sharing — to reach users beyond the configured depth.
Position-based hierarchy is more flexible for organizations where reporting lines don't match management chains. Navigate to the same Hierarchy security settings and select Position hierarchy instead.
You then create Position records (in the model-driven app or via import) and structure them into their own tree using their self-referential parent relationship. Users are then associated with positions via their User record. A user at a "Regional Director" position gains visibility into records owned by users at positions that are below "Regional Director" in the position tree.
This is particularly useful when:
This is where precision matters. When hierarchical security is enabled and a manager (call them Alice) looks at a list view for a table where their security role grants them "Business Unit" level access, Dataverse extends that access to include records owned by users who report to Alice (up to the configured depth) — even if those users are in different business units.
Crucially: this is additive. Alice still sees everything she'd see under her base security role. Hierarchy security only ever adds access; it never removes it. And it only adds access down the reporting chain, never up.
The access Alice gains through hierarchy security is equivalent to read and append access for most configurations. Write, delete, and share permissions don't automatically flow through the hierarchy — those require explicit privilege grants in the security role.
For many scenarios, team-based access (assigning records to an owner team, then controlling team membership) provides a more predictable and auditable access model than hierarchy security. Hierarchy security is powerful when:
If your organization's management hierarchy frequently changes, be aware that reconfiguring the Manager field on user records immediately changes what those managers can access — there's no staging or review step.
Once a hierarchy relationship is designated, the Dataverse query engine understands tree traversal. This unlocks powerful query patterns that would otherwise require multiple round-trips or complex client-side logic.
The Under operator retrieves all records that are descendants of a given record in the designated hierarchy. Given a root node's GUID, Under walks the tree depth-first and returns every record at any level below that root.
<fetch>
<entity name="crf_organizationalunit">
<attribute name="crf_name" />
<attribute name="crf_parentunitid" />
<filter>
<condition attribute="crf_parentunitid"
operator="under"
value="{ROOT-UNIT-GUID}" />
</filter>
</entity>
</fetch>
Note
The Under operator is evaluated against the designated hierarchy relationship for the table. If you have multiple self-referential lookups, only the one marked hierarchical will respond to Under. Querying a non-designated self-referential lookup with Under will result in an error or unexpected behavior.
The EqualOrUnder variant includes the root node itself in the results:
<condition attribute="crf_parentunitid"
operator="eq-or-under"
value="{ROOT-UNIT-GUID}" />
And NotUnder returns records that are not descendants of the specified node:
<condition attribute="crf_parentunitid"
operator="not-under"
value="{ROOT-UNIT-GUID}" />
The same operator is available in OData-style Web API queries:
GET [environment]/api/data/v9.2/crf_organizationalunits
?$select=crf_name,_crf_parentunitid_value
&$filter=Microsoft.Dynamics.CRM.Under(
PropertyName='crf_parentunitid',
PropertyValue='{ROOT-UNIT-GUID}'
)
This is significantly more capable than trying to implement tree traversal in client-side code. Without hierarchy operators, fetching an entire subtree requires either recursive API calls (slow, chatty) or a pre-computed depth/path column strategy.
These operators are specifically designed for security filtering. They resolve relative to the currently executing user rather than a hard-coded GUID:
eq-userid — records owned by the current usereq-useroruserhierarchy — records owned by the current user or by users below them in the hierarchyeq-useroruserhierarchyandteams — same as above, plus records owned by teams the current user belongs to<fetch>
<entity name="crf_serviceorder">
<attribute name="crf_name" />
<attribute name="ownerid" />
<filter>
<condition attribute="ownerid"
operator="eq-useroruserhierarchy" />
</filter>
</entity>
</fetch>
This pattern is invaluable for building custom dashboards or reports that should automatically scope to a manager's purview without hardcoding GUIDs or requiring separate views per person.
Formula Columns and Rollup Columns in Dataverse: Calculated Data Without Code covers rollup columns in depth, but let's address how they interact specifically with hierarchical structures.
Dataverse rollup columns can aggregate values across child records in a one-to-many relationship. For example, on a Project table you might roll up the sum of estimated hours from all related Task records. This works through a standard one-to-many relationship — not through the hierarchy designation.
This is a critical distinction. A rollup column on Organizational Unit that sums a revenue field from related Service Orders will aggregate direct child relationships only — it will not walk the hierarchy tree and aggregate across all descendants.
If you need a true hierarchical rollup (aggregate all the way down the tree), you have three architectural options:
Option 1: Scheduled Power Automate flows. Build a flow that runs nightly, traverses the hierarchy using the Under operator, and writes an aggregated value back to each node. This works well for daily-refresh scenarios and avoids real-time computation costs.
Option 2: Plugin-based real-time rollup. Write a synchronous plugin that fires on create/update of the leaf-level records and walks up the parent chain, updating rollup fields on each ancestor. This provides real-time accuracy but adds write latency and requires careful handling of concurrent updates.
Option 3: Pre-computed path columns. Store a materialized path (e.g., /root-guid/child-guid/grandchild-guid/) on each record. This enables efficient subtree queries but requires maintenance whenever records are reparented.
Warning
The pre-computed path approach has a cascading update problem. If you reparent a node that has 10,000 descendants, every one of those descendants' path columns needs updating. For deeply populated hierarchies, this can be a significant write storm. Batch it carefully with retry logic and consider asynchronous processing via the Service Bus or Dataverse Elastic Tables for very large trees.
The Under operator triggers a recursive CTE on the underlying SQL database. For shallow, sparse hierarchies (say, 5 levels deep with fewer than 10,000 total nodes), this is fast. For deep, dense hierarchies, the performance degrades non-linearly.
Dataverse has a built-in depth limit on hierarchy queries: 100 levels maximum. Hitting this limit throws an error rather than returning partial results. In practice, if your legitimate business hierarchy exceeds 20-30 levels, reconsider your data model — you may be conflating structural depth with data volume.
The Dataverse query engine also supports indexing on the designated hierarchy relationship's lookup column. Ensure the column is indexed (it will be by default for designated hierarchy relationships), and monitor query execution using Auditing and Change Tracking in Dataverse for Compliance to identify queries that scan rather than seek.
The hierarchy visualizer is a built-in control in model-driven apps that renders the tree graphically. It's available on any table that has a designated hierarchy relationship. But getting it configured meaningfully requires several deliberate steps.
The visualizer is surfaced through a special view type: the Hierarchy View. To configure it:
The Quick View Form approach is essential — without it, the hierarchy tiles just show the record name, which is often insufficient context. Add 2-3 key fields (status, assigned user, a key metric) to make the tiles meaningful.
For more on Quick View Forms and how they render in model-driven apps, see Designing Model-Driven Forms: Sections, Tabs, Subgrids, and Quick View Forms.
In the model-driven app, when a user is on a view for a table with hierarchy settings configured, they'll see a Hierarchy button in the command bar (it may appear as a tree icon or under a menu depending on the app version). Clicking it switches from the standard list view to the hierarchy tree view.
The hierarchy tree renders from a root node. If the user is looking at a specific record, the visualizer shows that record's subtree. From a list view, the visualizer shows all root nodes (records with no parent) and allows expanding each.
Tip
If the Hierarchy button doesn't appear, verify two things: first, that the Hierarchy Setting has been published; second, that the app is including the hierarchy visualizer control. In model-driven apps, sometimes the control needs to be explicitly added or the app needs to be republished after hierarchy settings are saved.
Navigate to the Forms area of your table and create a new Quick View form (not a Main form). Design it with the 2-4 most important fields for at-a-glance hierarchy browsing. For an Organizational Unit, this might be:
Keep the Quick View form narrow — it renders inside a fixed-size tile in the hierarchy visualizer. Avoid long text fields, subgrids, or rich text columns. The tile format clips content that overflows.
Link this Quick View form in the Hierarchy Settings configuration. Once published, the tiles in the hierarchy visualizer will show this contextual information for each node.
The built-in hierarchy visualizer has real limitations you need to communicate to stakeholders:
For organizations that need richer hierarchy visualization — swimlane org charts, interactive node editing, custom node coloring based on record data — consider a custom PCF control. The Extending Model-Driven Apps with PCF Controls: Building and Deploying Custom Field and Dataset Components for Dataverse Forms and Views lesson walks through the PCF architecture.
Now we return to the scenario from the introduction: a field service firm with regions, districts, teams, and work orders. You need to model multiple levels of an organizational structure. Dataverse's hierarchy designation is limited to self-referential tables. What are your options?
Flatten all organizational levels into one Organizational Unit table. Add a crf_level choice column with values: Region, District, Team. The hierarchy relationship points from each record to its parent of any level.
Advantages:
Under queries, hierarchy security, the visualizerDisadvantages:
Key insight
For most field service, HR, and organizational hierarchy use cases, the single-table self-referential model is the right answer. The "awkwardness" of mixing levels in one table is resolved with good views, filtered lookups, and clear naming — all of which are straightforward in model-driven apps. The query and security benefits of hierarchy designation make this trade-off strongly worthwhile.
Create Region, District, Team tables with lookup relationships connecting them in a chain: Team → District, District → Region.
Advantages:
Disadvantages:
Under operator, hierarchy security, and the visualizer are all unavailableThis approach is sometimes called a "ladder" model. It's common, but it sacrifices all of Dataverse's native hierarchy machinery. You'll end up rebuilding that machinery in flows or plugins.
Use a self-referential Organizational Unit table for the hierarchy (with level designation), and separately maintain specialized tables (like Work Order) that have a lookup to Organizational Unit. This gives you:
Organizational Unit treeUnder, then query work orders filtered by those org unit IDsThe two-step query isn't as elegant as a single hierarchical query, but it's far more capable than the pure cross-table ladder model. For performance-sensitive scenarios, cache the subtree ID list (it changes infrequently) to avoid the first step on every query.
The interaction between hierarchy security and standard Dataverse Security: Business Units, Security Roles, and Teams requires careful design.
Hierarchy security never reduces a user's access. If Alice's security role grants her Organization-level read on Work Orders (she can see all work orders in the tenant), hierarchy security adds nothing on top of that — she already has maximum access. Hierarchy security becomes meaningful when base access is scoped lower: to the User level or Business Unit level.
The typical pattern for hierarchy security to be useful:
If base access is already at Business Unit or Organization level, hierarchy security has no practical effect.
These are two orthogonal dimensions of access control. Business Units organize users geographically or functionally, with access scoping to the Business Unit boundary. Manager hierarchy cuts across Business Unit lines — Alice's direct reports may be in different Business Units, and hierarchy security still grants Alice access to their records.
For many enterprise scenarios, this cross-Business-Unit access is exactly what you want: a regional VP overseeing multiple country-level Business Units. But for organizations where Business Unit isolation is a hard security boundary (e.g., for regulatory compliance between subsidiaries), this cross-BU access from hierarchy security can be a problem.
Warning
If your Business Unit structure represents legal entities, subsidiaries, or compliance boundaries (not just organizational grouping), test hierarchy security carefully. Enabling manager hierarchy can allow users to see records across Business Unit lines, which may violate your data residency or compliance requirements. In these cases, keep base security at Business Unit level and manage cross-BU access through explicit team membership rather than hierarchy security.
Column-Level Security and Record Sharing in Dataverse operates independently of hierarchy security. Hierarchy security grants record-level access — the ability to see that a record exists and read its standard fields. Column-level security controls whether specific sensitive columns (SSN, salary, performance rating) are visible even when a user has record access.
This layering is important: a manager enabled by hierarchy security to see their reports' records will still be blocked from salary columns if column-level security restricts those fields. Design your column-level security profiles with this in mind — don't assume that hierarchy security implies full field access.
In this exercise, you'll configure a complete hierarchical table, enable hierarchy security, and surface the visualizer in a model-driven app. Work in a Developer environment — do not use a production environment.
You're building a Territory Management app for a sales organization. Territories are structured hierarchically: National → Regional → District → Local. You'll model this as a single self-referential Territory table.
Create a new custom table named Territory with the schema prefix of your publisher (e.g., crf_territory). Set the primary column to crf_name.
Add these additional columns to the table:
crf_level — Choice column with values: National, Regional, District, Localcrf_annualtarget — Currency columncrf_status — Choice column: Active, InactiveNavigate to the Relationships section and add a Many-to-one relationship where the Related table is Territory itself. Name the relationship crf_territory_parentterritory. Name the resulting lookup column crf_parentterritoryid with a display name of "Parent Territory."
Set cascade behaviors: Delete = Restrict, Reparent = No Cascade.
After saving, return to the relationship and enable the Hierarchical setting. Publish the table.
In the classic solution explorer (navigate via the legacy interface), expand your Territory table and click Hierarchy Settings.
Create a new Hierarchy Setting. The hierarchy relationship should auto-populate with crf_territory_parentterritory.
Back in the modern maker experience, create a Quick View Form for the Territory table containing: crf_name, crf_level, crf_status, crf_annualtarget. Name this form "Territory Tile."
Return to the Hierarchy Setting and set the Quick View Form to "Territory Tile." Save and publish.
In the Power Platform Admin Center, navigate to your environment → Settings → Users + Permissions → Hierarchy Security.
Enable Hierarchy Modeling. Select Manager Hierarchy. Set Depth to 3.
Create three test users in your environment (or use existing users). Configure User B as a direct report of User A (set User A as User B's Manager on the User record). Configure User C as a direct report of User B.
Assign User A, B, and C security roles that grant User-level access to Territory records.
Log in as each user and create one Territory record owned by each user. Verify that logged in as User A, you can see all three Territory records (your own, User B's, and User C's). Logged in as User C, verify you can only see your own record.
Using the Power Platform Web API from the browser's address bar or a tool like Postman or the XrmToolBox FetchXML Builder, run this query to retrieve all Territories under a specific root record:
<fetch>
<entity name="crf_territory">
<attribute name="crf_name" />
<attribute name="crf_level" />
<filter>
<condition attribute="crf_parentterritoryid"
operator="eq-or-under"
value="{YOUR-ROOT-TERRITORY-GUID}" />
</filter>
</entity>
</fetch>
Create a 3-level deep territory tree (National → Regional → District), put your root National territory's GUID in the query, and verify the results include all levels.
Add the Territory table to your model-driven app via the app designer. Create a basic view showing Name, Level, Status, and Parent Territory.
Publish the app. Navigate to the Territory view in the app.
Look for the Hierarchy view icon in the command bar area. If it doesn't appear, re-publish your Hierarchy Settings, then republish the app.
Create at least five territory records with a two-level hierarchy. Switch to the Hierarchy view and verify the tree renders correctly with the Quick View tile showing the configured fields.
Check these in order:
The Under operator queries against the designated hierarchy relationship's column. Verify:
EntityMetadata via Web API: GET /api/data/v9.2/EntityDefinitions(LogicalName='yourtablename')?$select=HierarchyRelationshipName)value attribute is correctCheck:
Manager field populated)A record in a designated hierarchy table can only have one parent. If a record appears in unexpected places in the visualizer, data integrity is compromised — some process (import, bulk update, plugin) has set contradictory parent values. Use a view filtered to show records where the parent relationship creates a cycle or duplicate path, and correct the data. Consider adding the anti-circular-reference plugin logic described earlier.
Remember: standard rollup columns aggregate direct child records from a one-to-many relationship, not the full hierarchy subtree. If you're expecting hierarchical aggregation, you need custom logic. Verify whether you actually need subtree aggregation or just direct-child aggregation before building complex solutions.
You now have a complete picture of how Dataverse hierarchical relationships work — from the metadata designation that unlocks hierarchy-aware queries, through the security model that lets manager access propagate through user chains, to the visualizer control that makes the tree navigable in model-driven apps.
The critical mental models to retain:
Under operator does the heavy lifting for tree traversal queries. Use it in FetchXML and the Web API instead of recursive client-side code.For next steps, deepen your understanding of the security model by working through Model-Driven App Security: Configuring Security Roles, Field Permissions, and Team-Based Access for Table Data. If you want to extend the hierarchy visualizer with custom rendering, explore Extending Model-Driven Apps with PCF Controls: Building and Deploying Custom Field and Dataset Components for Dataverse Forms and Views. And if you need to import an existing hierarchy structure into Dataverse from a flat file or source system, Importing and Migrating Data into Dataverse: Excel Import, Dataflows, and Upserts covers the patterns for loading parent-child data while preserving referential integrity.