Tutorials/Power Apps/Building a Modern Dashboard Inside Model-Driven Apps with Custom Pages
Power Appsintermediate

Building a Modern Dashboard Inside Model-Driven Apps with Custom Pages

Break free from standard forms and views. Learn how to design beautiful, responsive custom pages in your model-driven app using canvas' full power.

NA
Narmer Abader
@narmer · Published June 3, 2026

Imagine a world where your model-driven app isn't just a sequence of forms and views, but a beautifully orchestrated dashboard. A single screen that shows your open tickets, recent customer feedback, and lets you filter everything by category without navigating away.

That world is here. Power Apps Custom Pages let you drop a full-blown canvas interface into your model-driven app. In this walkthrough, I will build an internal Help Desk dashboard to demonstrate exactly how it works. Every control, container, and navigation trick is fair game.

Our Scenario: The Help Desk Ticket Dashboard

The IT Help Desk team needs a command center. They do not want to jump between different views just to see what is going on. Instead, they want a single dashboard with three core panels:

  • Active Tickets – List of open incidents, sorted by creation date.
  • Recent Feedback – Latest satisfaction survey results.
  • Filter by Category – Allows the team to click a category and instantly filter the tickets and feedback.

The underlying Dataverse tables will be Incidents (Tickets), Feedback (Survey responses), and Categories (lookup table).

Step 1: Scaffold the Model-Driven App and Custom Page

Start inside Power Apps Studio. Create a new model-driven app based on the tables you want, or open an existing one.

  1. Click Add page.
  2. Select Custom page.
  3. Choose Create new custom page and name it Dashboard Page.
  4. Click Add.

Power Apps Studio will open the canvas editor. This is your blank slate.

Model-Driven vs Canvas

A custom page inherits the security context of the model-driven app. Users see only the data their role permits, and any navigation away from the page drops them into familiar model-driven forms or views.

Step 2: Make the Canvas Responsive

Model-driven apps are responsive by nature. Your custom page must behave the same way to feel native.

  1. Go to Settings (the gear icon) > Display.
  2. Set Scale to fit to Off. This is the single most important step. It allows the canvas to stretch and shrink with the browser window.
  3. Add a Horizontal container to the screen. Name it con_Main_Dashboard.

Set these properties on con_Main_Dashboard:

  • Height: App.Height
  • Width: App.Width
  • Gap: 40
  • PaddingLeft, PaddingRight, PaddingTop, PaddingBottom: 40
  • LayoutAlignItems: Stretch

This creates a flexible outer shell that always fills the available space.

Inside it, place three Vertical containers:

  • con_ActiveTickets
  • con_RecentFeedback
  • con_FilterCategories

To make them share the screen evenly, give each vertical container these properties:

  • FlexibleWidth: true
  • LayoutMinWidth: 360
  • FillPortions: 1
  • BorderColor: DarkGray
  • BorderStyle: Solid
  • BorderThickness: 1

Now when the browser is wide, you have three columns. When it shrinks below 1080px, they stack gracefully and the minimum width keeps your content readable.

Step 3: Populate the Active Tickets Panel

First, add your Dataverse tables to the custom page. Open the Data pane and add:

  • Incidents
  • Feedback
  • Categories

Inside con_ActiveTickets, add a Horizontal container as a header. Place a Label inside it (text: "Active Tickets") and an Icon (the + icon) for creating a new record.

Below the header, add a Vertical gallery. Name it gal_Tickets.

  • Items: Sort(Incidents, 'Created On', SortOrder.Descending)
  • Layout: Title, subtitle, and body.

Map the labels inside the gallery template:

  • Title (Text): ThisItem.'Incident Title'
  • Subtitle (Text): Text(ThisItem.'Created On', ShortDate)
  • Body (Text): ThisItem.'Category'.'Category Name'

If your category table includes images, insert an Image control inside the gallery template and set its Image property to ThisItem.'Category'.'Category Image'.

To make the gallery stretch and fill the remaining space in its parent container:

  • AlignInContainer: Stretch

Step 4: Enable Navigation from the Custom Page

A custom page acts as a launchpad for the rest of the model-driven app. You can navigate to a specific record, a blank new form, or a system view.

View a Record (Edit Mode)

Inside your gallery template, add a button called "Open". Set its OnSelect to navigate directly to the selected incident's main form:

Navigate(gal_Tickets.Selected)

When the user clicks this, the page transitions seamlessly to the standard model-driven incident form for that specific record.

Create a New Record

For the + icon in the header of con_ActiveTickets, use this formula in OnSelect:

Navigate(Defaults(Incidents))

This opens a blank model-driven form for the Incidents table. Data relationship mappings and default values from the parent context are applied automatically.

Open a System View

Add a "View All" link or button at the bottom of con_ActiveTickets. Use this formula:

Navigate('Incidents (Views)'.'Active Incidents')

The Navigate function can target views, records, and even other custom pages, making custom pages the perfect control center for your model-driven app.

Save & Publish Order

To test navigation, you must save and publish the custom page, then publish the model-driven app. Click the Publish button in the canvas editor, then switch to the model-driven app editor and publish again. Changes do not propagate until the parent app is published.

Step 5: The Feedback and Filter Panels

The other two vertical containers follow the same structural pattern.

Inside con_RecentFeedback, add a gallery bound to Sort(Feedback, 'Created On', SortOrder.Ascending). Display a rating score and the related incident name.

Inside con_FilterCategories, add a gallery bound to Categories.

The core feature here is cross-filtering.

We need a global variable to hold the currently selected category. In the OnSelect property of the category gallery, write:

powerfxToggle the selected category
If(
  gblSelectedCategory.Category = ThisItem.Category,
  Set(gblSelectedCategory, Blank()),
  Set(gblSelectedCategory, ThisItem)
)

Now apply a filter to the other two galleries.

Active Tickets Gallery Items:

powerfxFilter Tickets by Selected Category
Sort(
  Filter(
      Incidents,
      gblSelectedCategory.Category = ThisItem.'Incident Category'.Category || IsBlank(gblSelectedCategory)
  ),
  'Created On',
  SortOrder.Descending
)

Recent Feedback Gallery Items:

powerfxFilter Feedback by Selected Category
Sort(
  Filter(
      Feedback,
      gblSelectedCategory.Category = ThisItem.'Feedback Category'.Category || IsBlank(gblSelectedCategory)
  ),
  'Created On',
  SortOrder.Ascending
)
Delegation Warning

Filtering on lookups across multiple tables can hit delegation limits. If your Incidents table has over 2,000 rows, consider narrowing the default gallery query — for example, CreatedOn >= DateAdd(Now(), -30, Days) — to stay within the delegation threshold.

Common Mistakes & Troubleshooting

  • Scale to fit is ON. The custom page will look tiny or stretched. Turn it off immediately as the first step.
  • Navigating without publishing. Changes to a custom page are not reflected in the model-driven app until the parent app is published.
  • Hardcoded sizes. Never use fixed pixel heights for the main containers. Use App.Height and App.Width or flexible properties to handle varying screen sizes.
  • Missing data sources. A custom page has its own independent data source list. Add the required tables or connections even if they exist elsewhere in the model-driven app.
  • Delegation. The Navigate function itself is delegation-safe, but the data queries feeding the galleries must follow standard Power Apps delegation rules.

Final Recommendation

Custom pages represent the single biggest leap in model-driven app flexibility since the platform launched. They allow you to build exactly the UX your users need without abandoning the security, relationships, and business logic of Dataverse.

Start small. Replace a complicated dashboard view with a single custom page. Then gradually expand. The pattern shown here — a responsive three-column layout with cross-filtering and deep navigation — can be adapted for sales dashboards, service portals, or project management hubs.

Give it a try on your next project. The formula is simple: a responsive container layout, a few galleries, smart navigation formulas, and a global variable for context. You might never look at a standard view the same way again.

References