As someone in love with SPFx web parts, Power Pages web templates felt like something I’d want to try.
What we’re building:
Analytical dashboard tiles for a book tracking application. Each tile will:
- Connect to Dataverse using FetchXML
- Accept a status code as a parameter
- Display a count of books matching that status for the current user
App overview (drawing, table schema)
My 5-year-old Procreate license is finally seeing some action:

Obviously, there’s no way to improve this perfection, so let’s move on to the database schema.
Table Structure
This solution uses the following table structure:

We only need the User Book Status table for our dashboard:
- Which books the user has
- What status each book is in (Read, Reading, Want to Read, etc.)
- The link to the user (Contact)
Creating a Web Template
Quick setup: Power Pages Management → Web Templates → New Web Template. Name it, connect to your site, set MIME type to application/json

Done with the basics, we can move to the actual web template building 😈
FetchXML Block
First things first, create table permissions for each table you’ll use. Go to Power Pages Management → Table Permissions, add your table, set the scope and assign roles. Without this, FetchXML will return nothing, even if you’re god and emperor in your environment.
Our FetchXML code:
{% assign status_value = status | default: '455810000' %}
{% assign color = card_color | default: '#4ade80' %}
{% fetchxml books %}
<fetch aggregate="true">
<entity name="adrbp_userbookstatus">
<attribute name="adrbp_userbookstatusid" alias="bookcount" aggregate="count" />
<filter type="and">
<condition attribute="adrbp_status" operator="eq" value="{{ status_value }}" />
<condition attribute="adrbp_user" operator="eq" value="{{ user.id }}" />
</filter>
</entity>
</fetch>
{% endfetchxml %}
We set table User Book Status, configured to return count and filtering by current user and dynamic status.
Now you can pass parameters:
{% include "Dashboard Stat" status: '455810000' title: "Read" secondary_title: "Finished books" card_color: "#b6d7a8" card_icon: "✅" %}
We can play with fetchxml code a little, but with some limitations.
What you can pass as parameters:
- condition values (current config)
<condition attribute="adrbp_status" operator="eq" value="{{ status_value }}" />
- optional conditions (on/off)
Note: if you’ve set a default value with| default:, the variable will never be empty, so the{% if %}check won’t work as expected. Remove the default to make this approach work.
<filter type="and">
<condition attribute="adrbp_user" operator="eq" value="{{ user.id }}" />
{% if status %}
<condition attribute="adrbp_status" operator="eq" value="{{ status }}" />
{% endif %}
</filter>
- comparison logic via {% if %} blocks
{% if filter_type == 'active' %}
<condition attribute="adrbp_status" operator="ne" value="455810004" />
{% elsif filter_type == 'inactive' %}
<condition attribute="adrbp_status" operator="eq" value="455810004" />
{% endif %}
Layout
HTML Structure
The HTML block is straightforward - we use Liquid variables (in double curly brackets) to insert dynamic values, and CSS custom properties to pass colors to our styles.
<div class="stat-card" style="--card-color: {{ color }};">
<div class="stat-content">
<div class="stat-title">{{ title }}</div>
<div class="stat-value">{{ books.results.entities[0].bookcount }}</div>
<div class="stat-subtitle">{{ secondary_title }}</div>
</div>
<div class="stat-icon">
{{ card_icon }}
</div>
</div>
As I wanted a different color for each card, I passed inline style="--card-color: {{ color }};" to feed each card’s unique color into CSS.
CSS Styling
The CSS classes need to be defined in your site’s styles. You can add them with any of these options:
- Custom CSS file (Styling workspace)
- Page-specific styles
- Global CSS file
.stat-card {
padding: 20px;
border-radius: 12px;
/* …… */
}
.stat-icon {
background-color: color-mix(in srgb, var(--card-color) 20%, white);
padding: 12px;
/* …… */
}
/* ... other styles ... */
The --card-color custom property sets each card’s unique color, and lets us create color variations (like the lighter icon background) from that same base color.
Full code for the impatient: GitHub
Manifest
Manifest is a JSON object which represents component configuration.
{% manifest %}
{
"type": "Functional",
"displayName": "Display Name",
"tables": ["table1_logical_name", "table2_logical_name"],
"params": [...]
}
{% endmanifest %}
Notes:
- The type can be either
FunctionalorLayout. UseFunctionalfor custom components (like our book card). UseLayoutonly for page templates and structural elements. - The tables property is developer-defined in the manifest, not user-configurable. Unlike params, users cannot change which tables the component connects to.
- Parameters are always strings. You can convert them to boolean, decimal, integer or string (maybe you just love converting strings to strings) using Liquid Type filters.
{% assign items_count = count | integer %}
So this is what our component’s manifest ends up looking like:
{% manifest %}
{
"type": "Functional",
"displayName": "Book Status Card",
"description": "Shows books by status",
"tables": ["adrbp_userbookstatus"],
"params": [
{
"id": "title",
"displayName": "Title"
},
{
"id": "secondary_title",
"displayName": "Secondary Title"
},
{
"id": "status",
"displayName": "Status",
"description": "Status to filter"
},
{
"id": "card_color",
"displayName": "Card Color",
"description": "Card color, use hex in a format #xxxxxx"
},
{
"id": "card_icon",
"displayName": "Card Icon",
"description": "Card icon"
}
]
}
{% endmanifest %}
And now, how it looks when you try to update web template’s config in UI:
![]()
Putting It All Together
Once our web template is ready, we can add it to a page like this:
{% include "Dashboard Stat" status: '455810000' title: "Read" secondary_title: "Finished books" card_color: "#b6d7a8" card_icon: "✅" %}
{% include "Dashboard Stat" status: '455810001' title: "Reading" secondary_title: "Currently reading" card_color: "#a8d7c0" card_icon: "📖" %}
{% include "Dashboard Stat" status: '455810002' title: "Want to Read" secondary_title: "On your wishlist" card_color: "#f5c6a8" card_icon: "⭐" %}
{% include "Dashboard Stat" status: '455810004' title: "Dropped" secondary_title: "Set aside" card_color: "#f48fb1" card_icon: "🚫" %}
Each {% include %} creates a reusable card with different data, so it is clean and easy to maintain.
Final Result
And here’s our book app home page - no difference from the initial design:
