Learn how to configure one-to-many and many-to-many relationships in Dataverse, understand cascade behaviors that protect your data integrity, and see exactly how relationships surface inside model-driven app forms. This is the structural knowledge every Power Apps maker needs.

Picture this: you're building a model-driven app to manage a consulting firm's projects. You've got a Projects table, a Tasks table, a Contacts table, and a Documents table. Everything looks fine in isolation — but when you start thinking about how these tables connect, things get complicated fast. Who owns which tasks? Can a task belong to more than one project? What happens to all the tasks when a project is deleted? These aren't just philosophical questions. They're structural decisions that will either make your app bulletproof or turn it into a tangled mess of orphaned records and broken lookups.
That's exactly what this lesson is about: table relationships in Dataverse. A relationship is a formal link between two tables that tells Dataverse (and your app) how records in those tables are associated with each other. Get relationships right and your app becomes intuitive, data-consistent, and easy to extend. Get them wrong and you'll spend your time firefighting data integrity issues instead of building features.
By the end of this lesson, you'll be able to look at a real business scenario, identify the correct relationship type to use, configure it in the Power Apps maker portal, understand exactly what cascade behaviors do and when to change them, and know how relationships surface inside model-driven app forms through subgrids and lookup columns.
What you'll learn:
You should be comfortable with the basics of Dataverse tables, columns, and rows before working through this lesson. If terms like "table," "column," "lookup," or "schema name" are new to you, start with Dataverse Fundamentals: Tables, Columns, and Rows Explained for Power Apps Makers first. It also helps to have read about Designing a Dataverse Data Model: Relationships, Lookups, and Choice Columns, which covers the strategic thinking behind data modeling. Some familiarity with model-driven app forms will help when we discuss how relationships surface in the UI — if you need a primer, check out Building Your First Model-Driven App: Site Map, Tables, Forms, and Views.
Before we jump into the types, let's establish a mental model. In Dataverse, a relationship is a mechanism that links rows in one table to rows in another. Under the hood, relationships are implemented using a special kind of column called a lookup column (sometimes called a foreign key in traditional database language). That column stores the unique identifier of a related record.
For example, if you have a Task row linked to a Project, the Task row contains a lookup column called something like cr123_project that holds the GUID (globally unique identifier) of the parent Project row. Dataverse uses that reference to enforce the relationship, display related records on forms, and determine what happens when a record is deleted.
This matters because it means relationships are directional. One table "owns" the lookup column, and that asymmetry determines a lot about how the relationship behaves.
A one-to-many (1:N) relationship connects one row in a parent table to potentially many rows in a child table. Think of it this way: one Project can have many Tasks, but each Task belongs to exactly one Project.
In Dataverse terminology:
The lookup column always lives on the child table. So in our example, the Task table gets a lookup column pointing to Project. Every task knows which project it belongs to. The project itself doesn't store task references — instead, Dataverse queries all tasks where the project lookup matches the current project's ID.
To create a 1:N relationship in the Power Apps maker portal:
Tip
Always review the auto-generated schema name before saving. Dataverse will suggest something like cr123_project_task_cr123_projectid, which can be unwieldy. You can shorten it to something like cr123_project_task for cleaner code and flow references later.
When you save, Dataverse creates the lookup column on the Task table automatically. If you navigate to the Task table and look at its Columns tab, you'll see the new lookup column listed there.
Once you create the relationship, it surfaces in two complementary ways inside your model-driven app:
On the Task form: The lookup column appears as a searchable lookup field where users can select the parent Project. Users type a few letters and a dropdown of matching Project records appears.
On the Project form: You can add a subgrid — a mini table view embedded in the form — that shows all related Tasks. This is one of the most powerful UI patterns in model-driven apps. The subgrid displays, filters, and lets users interact with child records directly from the parent record's form. To learn how to add and configure subgrids on your forms, see Designing Model-Driven Forms: Sections, Tabs, Subgrids, and Quick View Forms.
You'll sometimes see the term many-to-one (N:1) relationship. This isn't a separate type — it's the same 1:N relationship viewed from the child table's perspective. When you're on the Task table and you add a relationship, you choose "Many-to-one" and select Project as the related table. The result is identical to creating a "One-to-many" from the Project table.
The distinction matters practically when you're configuring relationships inside the table designer, because the maker portal shows you both N:1 and 1:N relationships in the Relationships tab, labeled by direction. Getting comfortable reading both directions will save you confusion.
Sometimes neither table is strictly the parent. Consider the relationship between Contacts and Projects in our consulting firm scenario. A contact (say, a client stakeholder) can be associated with multiple projects. And a project can have multiple contacts. Neither table "owns" the relationship. This is a many-to-many (N:N) relationship.
Dataverse implements N:N relationships using an intersect table (also called a junction table or bridge table). This is a hidden table that Dataverse creates and manages automatically. It contains two lookup columns: one pointing to each of the two main tables. Each row in the intersect table represents one connection — one Contact linked to one Project.
You never interact with the intersect table directly in most scenarios. Dataverse handles inserts and deletions there whenever users associate or disassociate records in the app UI.
Note
Unlike 1:N relationships, N:N relationships don't create a lookup column on either main table. Instead, they create an entirely separate intersect table. This means you can't filter or sort N:N related records using a simple lookup column — you'll need to work through the intersect table if you're writing advanced queries or flows.
Here's a judgment call that trips up many new makers. Should you use a built-in N:N relationship, or should you create your own intersect table manually?
Use the built-in N:N when:
Create a manual intersect table when:
In that second case, you'd create a "Project Role" table with a lookup to Project and a lookup to Contact, plus a choice column for Role. You now have two 1:N relationships instead of one N:N, but your data model is richer and more flexible.
Key insight
The built-in N:N is convenient but inflexible. If there's any chance the relationship will need its own metadata in the future, build the intersect table yourself from the start. Retrofitting this later is painful.
This is where the real power — and the real danger — of Dataverse relationships lives. Cascade behaviors define what Dataverse automatically does to child records when something happens to the parent record. They control four operations: Assign, Share, Unshare, and Delete.
Let's use our Project → Task relationship to walk through each.
Delete — What happens to Tasks when a Project is deleted? Assign — What happens to Tasks when the Project's owner changes? Share — What happens to Tasks when someone shares the Project with another user? Unshare — What happens to Tasks when sharing on the Project is removed?
For each trigger, you can choose one of these behaviors:
Cascade All — The action cascades to every related child record. Delete the project → all tasks are deleted. Reassign the project → all tasks are reassigned.
Cascade Active — The action cascades only to child records in an active state. Inactive/completed tasks are left alone.
Cascade User-Owned — The action cascades only to child records owned by the same user as the parent.
Cascade None — No automatic cascade. Child records are unaffected. This can leave orphaned records — tasks with a project lookup pointing to a deleted project.
Remove Link — Only available for Delete. Instead of deleting the child records, Dataverse clears the lookup column value, effectively orphaning the child but keeping it alive.
Restrict — Only available for Delete. Prevents the parent from being deleted if any child records exist. This is your safety net when you absolutely cannot have orphaned records.
Let's think through our scenario carefully:
For Delete, if you're okay with tasks being removed when their project is removed, use Cascade All. If tasks could have independent value beyond the project (say, they're linked to billing records), use Restrict to force users to manually clean up tasks before deleting a project.
For Assign, Cascade All is usually the right choice for tasks — if you reassign a project to a new project manager, they probably need to own the tasks too. But for something like Documents attached to a Project, you might want Cascade None if document ownership is managed separately.
For Share/Unshare, this is deeply tied to your security model. If you're using record-level sharing to give individual users access to a project, cascading share to tasks ensures those users can also see and edit the tasks. See Dataverse Security: Business Units, Security Roles, and Teams for deeper context on why this matters.
To view or change cascade behaviors on an existing relationship:
Warning
Changing cascade behaviors on a relationship that already has data is risky. If you switch from Restrict to Cascade All on Delete, the next time someone deletes a parent record, all child records will be permanently deleted — with no warning to the user. Test behavior changes in a sandbox environment first, and document your reasoning clearly.
Relationships interact with Dataverse security in ways that often surprise new makers. By default, a user's access to a child record is determined by their security role's permissions on that table — not automatically inherited from the parent.
This means a user could have access to a Project but not be able to see its Tasks if their security role doesn't grant Task access. The cascade Share/Unshare behaviors help with this in record-sharing scenarios, but for role-based access, you need to configure permissions explicitly.
If you want Tasks to be automatically accessible whenever a user has access to the Project, you can use the Parental cascade behavior combined with role-based privileges, or rely on Model-Driven App Security: Configuring Security Roles, Field Permissions, and Team-Based Access for Table Data to set permissions at the appropriate depth (Organization, Business Unit, Team, or User).
Configuring a relationship in the table designer is step one. Step two is making sure it actually appears in your app in a useful way. Here's what you need to know.
After creating a 1:N relationship, the lookup column appears on the child table. But it won't automatically appear on the form — you need to add it manually. Open the child table's main form in the form designer and drag the lookup column onto the canvas from the column list in the right panel. When a user opens a Task record, they'll see a lookup field showing the current Project, with the ability to search and change it.
On the parent (Project) form, you can add a subgrid to display all related Task records. In the form designer, click the area where you want the subgrid, then in the component panel, add a Subgrid. Configure it to use the Task table and filter by the relationship. You can also set the default view that appears in the subgrid, which is a good reason to invest time in Creating and Customizing Views in Model-Driven Apps: Filters, Sorting, and Editable Grids — a well-crafted view in a subgrid makes a huge usability difference.
For many-to-many relationships, related records appear in an Associated View — a special view type that's automatically created when you configure an N:N relationship. This view shows all records from the related table that are currently linked to the parent. Users can associate and disassociate records using the toolbar buttons that appear in the associated view panel on the form.
Let's put this together with a practical exercise using the consulting firm scenario.
Scenario: You have three tables: Project, Task, and Contact. You need to:
Step 1: Create the Project → Task relationship
Step 2: Change cascade behavior to Restrict Delete
Step 3: Create the Project ↔ Contact N:N relationship
Step 4: Verify in the app
Tip
If the associated views or subgrids don't appear automatically after configuring relationships, you may need to add them manually in the form designer. Relationships create the data connections; you still control what appears in the UI.
Mistake 1: Choosing Cascade All for Delete without thinking it through The parental cascade behavior is the default for good reason, but it's a double-edged sword. Deleting a Project will silently and permanently delete all related Tasks, Documents, and any other child records. Always verify what cascade behavior is in place before deploying to production.
Mistake 2: Trying to create a relationship after importing mismatched data If you import data into a child table with lookup values that don't correspond to valid parent records, Dataverse will reject the rows. Plan your data model and create relationships before bulk importing. If you're dealing with existing data, the Importing and Migrating Data into Dataverse: Excel Import, Dataflows, and Upserts article covers strategies for handling this cleanly.
Mistake 3: Using N:N when you need a custom intersect table As discussed earlier — if the relationship itself has properties (role, start date, status), the built-in N:N won't support storing them. Build your own intersect table from the start.
Mistake 4: Forgetting to add lookup columns and subgrids to forms Creating a relationship doesn't automatically update your forms. You need to manually add lookup columns to child forms and subgrids to parent forms in the form designer. This is a surprisingly common source of "where did the relationship go?" confusion.
Mistake 5: Changing relationship schema names after deployment The schema name of a relationship (and the lookup column it creates) is permanent. If you named it something confusing, you're stuck with it — or you'll need to delete the relationship, rebuild it, and migrate existing data. Invest time in naming conventions before you save.
Warning
You cannot delete a relationship while it has data. You must first remove all values from the lookup column (or delete all child records), then delete the relationship. Plan your schema changes accordingly in development before you have real data.
Table relationships are the skeleton of any serious Dataverse data model. Without them, you're just storing isolated lists. With them, you create a connected system where data flows logically between records, forms become interactive and contextual, and your app can enforce real business rules through cascade behaviors.
Here's what we covered:
From here, a natural next step is understanding how Formula Columns and Rollup Columns in Dataverse: Calculated Data Without Code can leverage your relationships — for example, rolling up the count of open tasks per project, or calculating a project completion percentage based on related task statuses. You might also explore more advanced relationship patterns like polymorphic lookups, which let a single lookup column point to records in multiple different tables — covered in Polymorphic Lookups in Dataverse: Configuring Regarding Columns, Customer Columns, and Multi-Table Relationships in Model-Driven Apps.
Model-Driven Apps & Dataverse