Access team templates let you grant per-record permissions dynamically — without creating new security roles or managing individual record shares. This deep-dive lesson covers template configuration, form integration, Power Automate automation, and the performance trade-offs you need to know before deploying at scale.

Picture this: your company has just deployed a model-driven app for managing client contracts. Every contract record is owned by a specific account manager, and the base security roles are configured correctly — account managers can see their own records, directors can see everything in their business unit. But now the sales director comes to you with a requirement: "When a lawyer needs to review a contract, they need Read and Append access to that specific record — but only that one. And when the review is done, we revoke it. We don't want lawyers seeing the entire portfolio."
Your first instinct might be to create a new security role, or to share the record individually. Both of those paths get painful fast. A custom security role is too blunt — it grants access at the privilege level, not the record level. Individual record sharing (the PrincipalObjectAccess table) works, but it creates a maintenance nightmare at scale and has no first-class UI story in the app itself. What you actually need is Dataverse Access Teams — and more specifically, the Access Team Template pattern that lets you embed a team-management subgrid directly into a form, so that app users can dynamically add and remove collaborators on individual records without ever touching security roles or system administration.
By the end of this lesson, you will understand how Dataverse manages row-level permissions through access teams, how access team templates define the permission shape for those teams, how to enable a table for access team templates, and how to wire the whole thing into a model-driven form so your users have a first-class, self-service collaboration experience. You'll also understand where this pattern breaks down, how to automate team membership with Power Automate, and how to troubleshoot the non-obvious failure modes.
What you'll learn:
This lesson assumes you are comfortable with the following:
PrincipalObjectAccess table. The lesson on Column-Level Security and Record Sharing in Dataverse covers the underlying mechanics.Before we build anything, you need a clear mental model of where access teams fit in the broader permission hierarchy. Dataverse grants record access through four distinct mechanisms, and they stack:
Security Role privileges — The base layer. Defines what a user can potentially do with records of a given type (Create, Read, Write, Delete, Append, Append To, Assign, Share), scoped to User, Business Unit, Parent Business Unit, or Organization. This is the mandatory foundation — without a role granting at least User-level Read, nothing else matters.
Record ownership — Every record has exactly one owner (a user or an owner team). The owning user/team inherits all privileges for that record regardless of role scope. If your role says "User-level Read," you can still read every record you own.
Record sharing (PrincipalObjectAccess) — An explicit grant of specific rights on a specific record to a specific user or team. This is how the Share privilege works. The platform stores these in the PrincipalObjectAccess (POA) table.
Access team membership — A lightweight, record-scoped variant of sharing. When a user is added to an access team that is associated with a specific record, they receive whatever privileges the access team template defines — but only for that record.
Access teams are mechanically implemented as record sharing. When you add user Alice to the "Reviewers" access team on Contract #42, Dataverse creates or updates a row in the POA table granting Alice the template-defined rights on Contract #42. The access team acts as a named group that makes this per-record sharing manageable at scale.
This distinction matters: access team templates are not security roles. They can only grant rights that are a subset of what the user already possesses through their security role. If Alice's security role grants zero access to the Contract table, adding her to an access team on a Contract record does nothing. The template can only expand record-level access up to the ceiling of the base role, never bypass it.
Key insight
Access team templates define the shape of access (which privileges apply to the record), but the ceiling of that access is always the user's security role. A user with no base-level access to a table cannot gain access through an access team. This is a common source of confusion when access teams appear to "not work."
Dataverse has two fundamentally different kinds of teams, and confusing them is one of the most common architectural mistakes in model-driven app security design.
Owner Teams can own records. They have security roles assigned to them. They are persistent entities in the system — you create them in advance, assign roles, and records can be transferred to them. Owner teams are great for group-level record ownership, like "Sales Team West" owning all leads generated from a specific territory.
Access Teams cannot own records. They cannot have security roles assigned to them. They exist only to extend record-level access to additional users. There are two variants:
User-created access teams: Manually created by a system administrator, not tied to any specific record or table template. Useful for ad-hoc scenarios but require administrative overhead to manage.
Access team templates (system-managed access teams): Created automatically by Dataverse on a per-record basis when a user is added through the Access Team subgrid on a form. This is the pattern we're focusing on in this lesson, and the one most relevant to the "collaboration on specific records" use case.
The critical difference in the template pattern: you never manually create the team. You define a template that says "for the Contract table, a team using this template gets Read, Write, and Append rights." Dataverse then auto-creates individual teams as needed — one per record, per template — and manages their lifecycle. When the last member is removed, the team is automatically deleted. This is the mechanism that makes per-record collaboration scalable without admin intervention.
Warning
Do not conflate access team templates with the "Teams" you see in the main Teams list in the Power Platform admin center or in the legacy Security > Teams area in the classic interface. Those are both user-created teams (owner or access). The auto-provisioned teams created by access team templates are hidden from those interfaces by default and are managed through the record's associated subgrid.
Not every table supports access team templates out of the box. You must explicitly enable it on each table where you want this behavior. This is a table-level setting, and once enabled, it cannot be turned off — so be deliberate.
Navigate to make.powerapps.com, open your solution, and find the table you want to configure (for example, our Contract table — not the legacy Contract system table, but a custom table you've built). Open the table's properties.
In the modern maker portal, click on the table name to open it, then click the Properties button in the top-right area of the table editor. In the properties panel, expand Advanced options.
Look for the checkbox labeled "Access teams" (sometimes shown as "Enable access teams" or similar depending on which version of the maker experience you're on). Check it.
Save and publish the table.
Warning
Enabling access teams on a table is irreversible through the UI. Dataverse sets a flag on the table metadata (IsAccessTeamEnabled) that cannot be unset once published. Before enabling this on a production table, confirm that access team management is genuinely the right pattern for your scenario. On very high-volume tables (millions of rows), the POA table growth from access team use can become a performance consideration.
If you're working in a team environment or want to verify the change programmatically, you can check the table's metadata using the Dataverse Web API:
GET [your-org-url]/api/data/v9.2/EntityDefinitions(LogicalName='cr123_contract')?$select=IsAccessTeamEnabled
The response will include:
{
"IsAccessTeamEnabled": true,
"LogicalName": "cr123_contract",
"@odata.context": "..."
}
This is useful to confirm the setting propagated correctly, especially if you're building ALM pipelines and need to assert table configuration before deploying dependent templates.
The access team template is the heart of the pattern. It defines two things:
You configure templates in the classic Settings area of a model-driven app, not in the modern maker portal — at least as of the time of writing. Navigate to your model-driven app (or the Settings hub), then go to Settings > Security > Access Team Templates. If you're in a classic Dynamics 365 interface, it's under Settings > Security > Access Team Templates.
Click New to create a new template. Fill in the fields:
Next, configure the Access Rights section. You'll see a grid of the eight Dataverse privileges. Check the boxes that correspond to what a team member should be able to do on the specific record they're added to:
| Privilege | Purpose |
|---|---|
| Read | View the record |
| Write | Edit the record |
| Delete | Delete the record |
| Append | Associate other records to this record |
| Append To | Allow other records to be associated to this record |
| Assign | Change record ownership |
| Share | Share the record with others |
| Create | Create new records (note: this is NOT record-scoped — see below) |
Warning
The Create privilege in an access team template is a known gotcha. Unlike the other privileges, Create cannot be meaningfully scoped to "this record" because a record doesn't exist yet when you're creating it. If you include Create in a template, it effectively gives the user organization-level Create access for that table. In most scenarios, you should leave Create unchecked in templates and grant it instead through the user's base security role if they need it.
For the "Contract Reviewer" scenario: check Read, Append, and Append To. This lets the lawyer read the contract, associate notes or activities to it, and be associated from other records — without being able to modify the contract terms or delete the record.
Save the template.
Access team templates are solution-aware components in modern Dataverse. You can add a template to your solution by navigating to your solution, clicking Add Existing, and selecting Access Team Template from the component picker. This ensures the template travels with your solution through dev/test/prod environments.
Tip
If you're migrating access team templates between environments as part of a managed solution, the template's GUID is preserved. This matters because the Access Team subgrid on the form references the template by its GUID. If the GUID changes between environments (which can happen with unmanaged export/import), your form's subgrid configuration breaks silently — users see an empty subgrid with no way to add members. Always transport templates inside your solution rather than exporting them separately.
With the table enabled and the template defined, the final configuration step is adding the Access Team subgrid to the record form. This is what gives your users the ability to manage team membership directly on a record, without any system administration involvement.
In your solution, navigate to the Contract table, open Forms, and open the main form where you want the subgrid to appear. Choose a logical location — typically a dedicated Collaboration or Access tab, separate from the core data fields.
If you need guidance on adding sections and tabs to forms, refer to Designing Model-Driven Forms: Sections, Tabs, Subgrids, and Quick View Forms.
In the modern form editor, click the area where you want the subgrid, then click Component in the left pane and select Subgrid. Alternatively, in the classic form editor, drag a Subgrid from the Insert tab.
When the subgrid properties dialog opens, configure as follows:
Wait — this seems counterintuitive. You're adding a subgrid pointing at the User table, not at some "Access Team" junction table. Here's why: Dataverse renders the Access Team subgrid as a specialized control that happens to be configured via a standard subgrid property panel. The actual rendering logic is driven by the Access Team Template association, which you configure in the next step.
After adding the subgrid, look for the subgrid's additional properties. In the modern form editor, with the subgrid selected, look at the right-side property panel. You should see a property called Team template or look for it under the subgrid's advanced settings.
Select your "Contract Reviewer Access" template from the dropdown. This is the link that tells the form: "When rendering this subgrid on a Contract record, use this access team template to display and manage membership."
If the dropdown shows no templates, double-check that:
Give the subgrid a meaningful label. "Contract Reviewers" is better than "Users Subgrid 1." Under Show label, enable it so users understand what the subgrid represents.
You can also control the subgrid's height (number of rows displayed), whether it shows a search box, and whether it shows a chart. For access team subgrids, you typically want a compact display with 5-10 rows and no chart.
Save and publish the form. Open a Contract record in your model-driven app and you should now see the "Contract Reviewers" subgrid section.
When a record owner (or anyone with Share privileges on the record) opens a Contract, they see the "Contract Reviewers" subgrid. The subgrid has:
+ icon in the subgrid toolbar)Clicking Add User opens a standard user lookup. The record owner searches for the lawyer by name, selects them, and clicks Add. Dataverse:
The lawyer can now open Contract #42 and see it — assuming their base security role doesn't already give them that access (in which case access teams are redundant). When the review is complete, the record owner clicks the user row and clicks Remove. Dataverse removes the user from the team, deletes the POA row, and if no members remain, deletes the auto-created team. The lawyer can no longer see the record.
Key insight
The Access Team pattern delegates permission management to record owners and app users, not system administrators. This is intentional and is a significant operational benefit. The trade-off is that you're trusting record owners to make appropriate access decisions. For high-sensitivity data, you may want to supplement this with audit logging — see Configuring Dataverse Auditing and Field-Level Change History in Model-Driven Apps for how to track team membership changes.
The manual "add user via subgrid" workflow is clean for ad-hoc collaboration, but many real-world scenarios require automatic team membership based on business logic. Examples:
Power Automate has native actions for managing access team membership via the Dataverse connector.
In a Power Automate flow using the Microsoft Dataverse connector, you'll find:
But here's the catch: these actions work on team IDs, and for access team templates, the team is auto-created and has a dynamically assigned ID. You need to either:
team tableGrantAccess and RevokeAccess actions (which bypass teams entirely and operate directly on the POA table)The most reliable approach for automation is to use the Perform an unbound action (or the specific action connector) to call GrantAccess. This directly creates the POA entry without needing to manage the team manually:
POST [org-url]/api/data/v9.2/GrantAccess
Content-Type: application/json
{
"Target": {
"@odata.type": "Microsoft.Dynamics.CRM.cr123_contract",
"cr123_contractid": "12345678-1234-1234-1234-123456789012"
},
"PrincipalAccess": {
"Principal": {
"@odata.type": "Microsoft.Dynamics.CRM.systemuser",
"systemuserid": "abcdefab-abcd-abcd-abcd-abcdefabcdef"
},
"AccessMask": "ReadAccess, AppendAccess, AppendToAccess"
}
}
AccessMask is a comma-separated string of the privilege names. The valid values are: ReadAccess, WriteAccess, DeleteAccess, AppendAccess, AppendToAccess, AssignAccess, ShareAccess, CreateAccess.
In Power Automate, use the Perform an unbound action action, set the Action name to GrantAccess, and pass the JSON body above with dynamic values filled in from trigger data.
Tip
When using GrantAccess through the Dataverse connector in Power Automate, use the Perform an unbound action action from the Microsoft Dataverse connector (the modern one, not the legacy Common Data Service connector). Pass the Target and PrincipalAccess as a JSON body. If you're also managing access team templates and want the subgrid to reflect the membership, you should use the team-based approach (Approach 2) instead, because GrantAccess creates a direct share entry that the template subgrid won't display as a team member.
If you need the team membership to be visible in the Access Team subgrid, you must actually add users to the auto-created team. Here's the query to find whether an access team for a given record and template already exists:
GET [org-url]/api/data/v9.2/teams?$filter=teamtype eq 1
and regardingobjectid_cr123_contract/cr123_contractid eq '12345678-1234-1234-1234-123456789012'
and teamtemplateid/teamtemplateid eq 'template-guid-here'
&$select=teamid,name
teamtype eq 1 filters for access teams specifically (owner teams are teamtype eq 0). The regardingobjectid_* filter uses the polymorphic lookup on the team record that links it to the associated record.
In Power Automate, this becomes:
teams table with the filter aboveCreating access teams programmatically requires setting the correct fields:
{
"teamtype": 1,
"name": "Contract #42 - Contract Reviewer Access",
"teamtemplateid@odata.bind": "/teamtemplates(template-guid-here)",
"regardingobjectid_cr123_contract@odata.bind": "/cr123_contracts(record-guid-here)"
}
Warning
The team name field for auto-created access teams must be unique within an environment. Dataverse generates a system name when teams are created via the UI, but if you're creating them programmatically, you need to ensure uniqueness — typically by including the record ID or a record identifier field in the name. Duplicate names will cause a creation error.
A common real-world automation: when a Business Process Flow moves to "Legal Review" stage, add all users with the "Legal Reviewer" security role to the access team.
The trigger in Power Automate is When a row is added, modified, or deleted on the BPF entity (e.g., cr123_contractprocess), filtered on the activestageid column. Then:
systemuserroles to roles)GrantAccess or find/create the access team and add the userQuerying users by security role requires a slightly non-obvious endpoint:
GET [org-url]/api/data/v9.2/roles(role-guid-here)/systemuserroles_association?$select=systemuserid,fullname
This traverses the many-to-many relationship between roles and users.
A single table can have multiple access team templates, each defining a different privilege set. This is powerful: you can have "Observers" (Read only), "Collaborators" (Read + Write + Append), and "Approvers" (Read + Write + Share) as three separate templates on the same Contract table.
Each template gets its own subgrid on the form — or you can put them on separate tabs if the form would otherwise become cluttered. From a user experience perspective, clearly labeled sections ("Who can view this contract?", "Who can edit this contract?") communicate the access model to business users better than a single monolithic team list.
From a data model perspective, each template generates a separate auto-created team per record. A single Contract record with three templates and active membership in all three results in three team records and three (or more) POA rows in the system. This is fine at normal scale; it becomes a consideration at very high record counts.
Key insight
You can add up to 5 access team templates per table. This is a platform limit, not a soft guideline. Plan your template architecture accordingly. If you find yourself needing more than 5 distinct permission shapes on a single table, it's a signal that you may be using access teams to solve a problem better addressed by a more nuanced data model — perhaps splitting records into different tables, or rethinking your security role hierarchy.
Understanding how access team permissions interact with security roles is critical for getting the behavior you expect.
The Floor: A user must have at least some base access to the table through their security role for access team membership to matter. The most common scenario is a user with "User-level Read" on Contracts. They can see their own records. When added to an access team for a record they don't own, they gain the template-defined rights (say, Read + Write) on that record.
The Ceiling: Access teams cannot grant rights that exceed the user's security role. If Alice's role grants only Read and the template includes Write, Alice gets only Read through the team. The platform takes the intersection of the template's rights and the user's role-level rights.
This has a practical implication: when designing your access team templates, think about what rights you're granting under the assumption that team members have a specific base role. Document that assumption. If you have users with different base roles being added to the same access team, the effective permissions they receive will differ — which can cause confusing "why can Bob edit this but Carol can't?" support tickets.
The "No Access" scenario: What if you want to share a record with someone who has no access to the table at all through their security role? Access teams cannot help you here. You'd need to either:
This is one of the limitations that makes access teams unsuitable for external sharing (with guest users or external parties). For external collaboration patterns, look at portals or custom API layers.
The POA table (PrincipalObjectAccess) is one of the most impactful tables for Dataverse query performance. Every time Dataverse evaluates whether a user can access a record, it checks the POA table for any sharing entries. At scale, this table can grow into tens or hundreds of millions of rows, and poorly designed access patterns make it worse.
Each user-template combination on each record creates one or more POA rows. If you have 100,000 active contract records, each with an average of 5 access team members across 2 templates, that's 100,000 × 5 × 2 = 1,000,000 POA rows just for contracts. This is within normal operating range for Dataverse.
The problematic scenario is when access teams are never cleaned up. If records stay active for years and team members accumulate without removal, the POA table grows continuously. Implement a regular cleanup process:
RevokeAccessUsers who access many records through sharing (POA) rather than through role-level access experience slower query performance because the platform must evaluate both the role-based access path and the sharing path for each query. If you have power users who legitimately need access to large numbers of records, it's more performant to give them a broader security role (business unit-level or org-level) and remove them from individual access teams.
Tip
Monitor your POA table size using the Dataverse analytics in the Power Platform admin center. Navigate to Resources > Dataverse Environments, select your environment, and review the table storage breakdown. If POA is in your top 5 tables by row count, you likely have a cleanup problem or a design problem worth investigating.
In model-driven app views, when a user's access is primarily through sharing (POA) rather than role-based access, the underlying RetrieveMultiple queries are significantly more expensive. The platform must join the POA table to find all records the user can see. This manifests as slow view loading for users who rely heavily on access teams for their record visibility.
If your design results in most users seeing most of their records via access team sharing rather than role-based access, you should reconsider whether access teams are the right mechanism. Access teams shine when they're supplementing a base role that already provides appropriate bulk access — not replacing role-based access entirely.
In this exercise, you'll configure a complete access team template pattern for a custom Project table and test it end-to-end with two user accounts.
cr_project with at least Title and Status fieldscr_projectcr_projectOpen your solution in make.powerapps.com, navigate to the cr_project table, and open its Properties. Under Advanced options, enable "Access teams." Save and publish.
Navigate to the classic Settings area (use the gear icon in a model-driven app or navigate via the URL to /main.aspx#settingsecurity). Open Security > Access Team Templates.
Create a new template:
Save the template. Note the template's ID from the URL for later reference.
Add the template to your solution: in your solution, click Add existing > Access Team Template and select "Project Consultant Access."
In your solution, navigate to the cr_project table > Forms > Main form.
Add a new section to the form. Label it "Project Team Members."
Inside that section, add a Subgrid component. Configure:
Save and publish the form.
Log in as User A (Project Manager). Create a new Project record with Title = "Website Redesign 2024."
On the record form, find the "Consultants with Access" subgrid. Click the + button. Search for User B and add them.
Open a private/incognito browser window and log in as User B (Consultant). Navigate to the Project table in the model-driven app.
Verify: User B should be able to see "Website Redesign 2024" — this is the access team grant working. User B should not see any other Project records.
Return to the User A session. In the "Consultants with Access" subgrid, select User B's row and click Remove.
Refresh the User B session. User B should no longer see the "Website Redesign 2024" record.
As a system administrator, use the Dataverse Web API to confirm the sharing entry:
GET [org-url]/api/data/v9.2/principalobjectaccessset?$filter=objectid eq <project-record-id> and principalid eq <user-b-id>&$select=accessrightsmask,principaltypecode
When User B is added, this should return a row with an accessrightsmask corresponding to Read + Write. When User B is removed, the row should be gone (or have a zero mask).
The "+" button in the access team subgrid is only visible to users who have the Share privilege on the record. If the record owner's security role doesn't include Share at User-level or higher for the table, they can see the subgrid but can't manage it. Check the security role and ensure Share is enabled.
The team template dropdown in the subgrid configuration only shows templates for the current table. If the dropdown is empty, verify:
This is almost always the base role problem described earlier. The user has no security role that grants any access to the table, so the access team grant has no floor to stand on. Check the user's effective security roles and confirm they have at minimum User-level Read for the table through their role, even if that only covers their own records.
If you used GrantAccess directly (not via team membership), the share entry exists in the POA table, but it's not associated with an access team. The subgrid only shows users who are members of the auto-created access team associated with the template. Use the team-based approach if you need the subgrid to reflect automated membership.
If you notice a large number of records in the team table with teamtype eq 1, you have auto-created access teams that may not have been cleaned up when records were deactivated or deleted. Implement a cleanup flow: when a record is deactivated, query for its associated access teams and delete them (which also removes the POA entries).
Deleting an access team via the Web API:
DELETE [org-url]/api/data/v9.2/teams(<team-id>)
This operation requires System Administrator or equivalent privilege. In a flow, use the service principal or an administrative user for this step.
If you transport your solution as managed but the access team template was previously in the environment as unmanaged (or vice versa), you can end up with GUID conflicts. The symptom is a subgrid that renders with the correct label but shows no + button and no members, even for records where members definitely exist. Delete the unmanaged template, import the managed solution, and then re-associate any existing teams to the new template GUID (a bulk update via the Web API may be needed).
Tip
For enterprise deployments, always treat access team templates as solution components from day one. Add them to your solution the moment you create them. Retrofitting them into solutions after they've been created unmanaged in production is a support headache that the early discipline of solution hygiene completely prevents.
One underutilized capability: access team templates work with parent records, and you can use Power Automate to cascade membership. If a user is added to an access team on a parent Account record, your automation can automatically add them to access teams on all child Contract records belonging to that account.
This creates a pseudo-hierarchical access model without actually using Dataverse's hierarchical security (which has its own limitations around performance and org chart modeling). The advantage is that it's entirely explicit and auditable — each POA entry is traceable. The disadvantage is that it requires active automation to keep in sync when the parent team membership changes.
For true hierarchical security scenarios — where access should flow down a management or organizational hierarchy automatically — look at Designing Dataverse Hierarchical Relationships and Rollup Security: Modeling Parent-Child Table Hierarchies, Hierarchical Security Roles, and Visualizing Tree Structures in Model-Driven Apps, which covers the native hierarchy security model and its trade-offs.
Access teams are elegant for their specific use case, but they're frequently overused. Here are the signals that you should look elsewhere:
Signal: Most users need access to most records. If 80% of users need access to 80% of records through access teams, the overhead of managing individual team memberships is enormous and the POA table will be enormous. Give those users a broader security role instead.
Signal: Access needs to mirror an org chart. Native hierarchical security (or owner teams organized by business unit) handles this better. Access teams require explicit membership management, which doesn't naturally reflect the org chart.
Signal: You need to share with external users. Access teams require the principal to be a licensed Dataverse user with a security role. External guests in Entra ID can be licensed as Power Apps users, but this adds cost and complexity. For external sharing, Power Pages or a custom API layer is typically more appropriate.
Signal: You're creating templates to work around insufficient security roles. If you find yourself creating access team templates because your security role design is too permissive or too restrictive, fix the security role design. Access teams should extend a correct base model, not compensate for a broken one. Start with the comprehensive guidance in Model-Driven App Security: Configuring Security Roles, Field Permissions, and Team-Based Access for Table Data before reaching for access teams.
Access team templates are one of Dataverse's most practical tools for real-world collaboration scenarios. They let you delegate per-record permission management to business users, keep your security role design clean and stable, and avoid the POA table sprawl that comes from ad-hoc record sharing. When implemented correctly — with a solid base role, a well-designed template, proper form integration, and automation that cleans up after itself — they're nearly invisible to end users and require almost no ongoing administration.
To recap what you can now do:
GrantAccess action or by managing auto-created team records directlyWhere to go next:
If you want to understand the full Dataverse security model that access teams sit within, Dataverse Security: Business Units, Security Roles, and Teams covers the complete picture. If you're building complex data models where the right access pattern depends on how tables relate to each other, Configuring Dataverse Table Relationships in Model-Driven Apps: One-to-Many, Many-to-Many, and Cascade Behaviors Explained will help you design relationships that make security configuration cleaner. And if you need to track who added or removed access team members for compliance purposes, Configuring Dataverse Auditing and Field-Level Change History in Model-Driven Apps shows you how to surface that audit trail.