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:

  1. The type can be either Functional or Layout. Use Functional for custom components (like our book card). Use Layout only for page templates and structural elements.
  2. The tables property is developer-defined in the manifest, not user-configurable. Unlike params, users cannot change which tables the component connects to.
  3. 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: