Where this stands in 2026
Still appliesStill works. The fetchxml Liquid tag still applies table permissions, and Microsoft’s docs now point to its xml attribute for exactly this kind of debugging. If you need a real data endpoint rather than a debugging tool, the Power Pages Web API is now the supported option.
Official docs for today’s approach
- Template tags: fetchxmlDocuments the xml attribute for debugging table permissions.
- Overview of the Power Pages Web APIThe supported way to read and write data from JavaScript.
- Run security scanA built-in, broader check of your site’s security setup.
Written in 2020 for PowerApps Portals. See what things are called now.
Microsoft has since renamed the product and most of its parts. The text keeps the original terms.
| In this note | Today |
|---|---|
| PowerApps Portals | Power Pages |
| Entity | Table |
| Entity Permission | Table permission |
| Web Form | Advanced (multistep) form |
| Entity List | List |
Overview
As discussed in my last post, utilizing Entity Permissions in Entity/Web Forms (sub-grids) and Entity Lists injects link-entity tags into the queries in order to build a relationship hierarchy back to the Contact or the Contact’s “Customer” field.
Similarly, using the fetchxml tag will always honor Entity Permissions and inject the links. Using this to our advantage, we can create a few quick and easy tools to help with debugging and/or understanding our Entity Permissions and Web Roles.
Set Up the Base Web Template
Add the following code snippet to a new Web Template titled “Permissions Query” with a MIME type of application/xml:
{%- assign etn = etn | default: params.etn | default: 'account' -%}
{%- assign pk = pk | default: params.pk | default: 'name' -%}
{%- assign show = show | default: params.show | default: false | boolean -%}
{%- assign recordid = etn | append: "id" -%}
{%- fetchxml query -%}
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
<entity name="{{ etn }}">
<attribute name="createdon" />
<attribute name="{{ recordid }}" />
{%- if show -%}<attribute name="{{ pk }}" />{%- endif -%}
<order attribute="createdon" descending="false" />
</entity>
</fetch>
{%- endfetchxml -%}
<PermissionsQuery>
<Query>
{{ query.xml }}
</Query>
{%- if query.results -%}
<ResultCount>{{ query.results.entities.size | default: 0 }}</ResultCount>
{%- if show -%}
<Results>
{%- for result in query.results.entities -%}
<Record>
<Key>{{ result[pk] | xml_escape }}</Key>
<Guid>{{ result[recordid] | xml_escape }}</Guid>
</Record>
{%- endfor -%}
</Results>
{%- endif -%}
{%- endif -%}
</PermissionsQuery>Note that you could change this to JSON or any other preferred output type.
The parameters work as follows:
- etn: Defines the Entity to query against [Default: account]
- pk: Defines the Primary Key for entities that do not use name, such as Contact or custom Entities [Default: name]
- show: Whether or not to show the results of the query [Default: false]
Option 1: Use as an API
Now that the Web Template is available, let’s set up an API. This API can either be consumed (query for it, act on the data) or displayed in the browser for simple viewing. A few steps will get things started:
- Create a Page Template – “Permissions Query”
- Type: Web Template
- Web Template: Permissions Query
- Use Website Header and Footer: False
- Create a Web Page – “Permissions Query”
- Parent Page: Home
- Partial URL: permissions
- Page Template: Permissions Query
- OPTIONAL: Create a Web Page Access Control Rule to prevent non-Admins from using it – “Permissions Query Admin Access”
- Web Page: Permissions Query
- Right: Restrict Read
- Web Roles: Administrators
Recreated illustration: Example of an XML-based API: the Permissions Query page returns the FetchXML for All Opportunities, including the link-entity and condition injected by the user’s Entity Permissions, plus a result count.
- Callout 1: The <condition> injected for the root permission: the Contact’s Company (parentcustomerid) must equal the user’s Account.
- Callout 2: The <link-entity> injected for the Parent permission, joining Opportunity to Contact through opportunity_parent_contact.
- Callout 3: ResultCount: the number of records this user’s combined Web Roles can actually read.
Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.
The image above is a visual representation of the manipulation to the query “All Opportunities” when the following Entity Permissions are associated with the user’s Web Roles:
- Contacts associated with my Account
- Entity Name: contact
- Scope: Account
- Account Relationship: contact_customer_accounts
- My Colleague’s Opportunities
- Entity Name: opportunity
- Scope: Parent
- Parent Entity Permission: Contacts associated with my Account
- Parent Relationship: opportunity_parent_contact
Option 2: Add to Existing Pages
With the way the Web Template has been built, it can easily be added to any other existing template. A good use case for this is when you have an Entity List template that you want to compare against both the query and the result set. Keep in mind that the Entity List must have Enable Entity Permissions set to True for the comparison to make sense, and any other differences will come from the filters of the view used in the Entity List.
To do this, simply use the include Liquid tag. An example is given below, with the tag added to a Web Template intended to show an Entity List. It uses the same URL parameters as the API, but only shows the query if the parameter “list” is set to “true”.
Optionally, you can also use Liquid to check whether the user has an Admin role to control the flow here.
{% extends 'Layout 1 Column' %}
{% block main %}
{%- assign etn = etn | default: params.etn | default: 'account' -%}
{%- assign pk = pk | default: params.pk | default: 'name' -%}
{%- assign show = show | default: params.show | default: false | boolean -%}
{%- assign recordid = etn | append: "id" -%}
{% include 'Page Copy' %}
{%- if params.list -%}
{%- capture permissions -%}{% include 'Permissions Query' etn:etn pk:pk recordid:recordid show:show %}{%- endcapture -%}
<pre lang="xml">{{ permissions | escape }}</pre>
{%- endif -%}
{% if page.adx_entitylist %}
{% include 'entity_list' key: page.adx_entitylist.id %}
{% endif %}
{% endblock %}Checking this Web Page with the same parameters as before, plus list=true, gives us the following example. Feel free to play around with other entities – e.g., ?list=true&etn=contact&pk=fullname&show=true to see all of the Contacts included in the Opportunity filter.
Recreated illustration: Example of Including in Another Template: an Entity List page with list=true shows the escaped Permissions Query output above the list, so the generated query and the visible records can be compared.
- Callout 1: The included Permissions Query template, escaped into a <pre> block because list=true is in the URL.
- Callout 2: The Entity List itself. With Enable Entity Permissions on, its rows match the query’s ResultCount.
Recreated illustration. The original screenshot was lost with the old site; this drawing follows the article text.
I hope you find this helpful, whether for understanding the security further or for triaging issues. I’ve had success sharing this with developers new to Dynamics & PowerApps Portals to help them understand their changes, and I’ve used it myself to understand how my combinations of Web Roles were impacting the list results I’d see.
Restored in 2026 from the Internet Archive at its original address. Figures are recreated illustrations of lost screenshots; the text was lightly copy-edited.