First Look at Generative Pages in Power Apps

With Generative Pages (find announcement and docs here) we can create a page in Model-Driven Apps using natural language. You can see it as a reimagined Custom Page with two differences:

  • You don’t need PowerFx to create it. You just need to explain what you want.
  • The result is React code, which you can see right inside the page.

Why do we need Generative Pages?

If you are a Power Apps maker, maybe you think:
I’ve learned already PowerFx, why would I need something new?“.

First of all, we can define the page much faster, by explaining it, without having to put it in formulas and place all the controls on the canvas.

In Custom Pages, we define the app using PowerFx, which is then translated in React. This additional PowerFx layer, needs to let you explain every possible requirement, add telemetry and handle all request possibilities. The Custom Pages are in the end much slower (in a lot of cases I’ve seen).

With Generative Pages you create directly the React code, which is optimized for your requirement. You get the best possible performance. It’s like you get access to the PCF power, but define it using your natural language.

If you are a React developer, maybe you think:
“I can do PCFs using React and TypeScript, I can vibe code using GitHub Copilot inside VSCode, why do I need Generative Pages?”

Of course we can achieve the same inside VSCode, but Generative Pages are designed to understand the Power Apps and Dataverse, so the AI can better understand what we want to create. We can directly define the tables, and the GenPage “Copilot” just knows the model and how the access the table. That’s why we are much faster with the page creation right inside the Power Apps. Besides that, we don’t loose time with boilerplate creation and upload, or with autoresponder tools, because we can directly test the page inside the designer.

Of course, not everything is perfect for now. And even with the power of vibe coding, the maker still needs to understand what happens in the background. We’ll talk about that later. Today let’s see how it works.

If you prefer the video version, you find it at the end of the post.

How to create a Generative Page?

There are a few pre-requisites (some of them are because this is a very early public preview stage, at the time I write the blog – July 2025):

  • The Power Platform environment must be located in the US region.
  • The “Enable new AI-powered Copilot features for people who make apps” environment setting must be enabled. More information: Copilot environment setting
  • This feature is being gradually rolled out across regions and might not be available yet in your region.

I would add, that you can use it only in Model-Driven Apps.

  1. It’s better to start with a Solution, and make sure to create your tables first
  2. Create or edit a Model-Driven App
  3. Click on “Add page” and choose “Describe a page”

You will land on the page creation start screen

Here you can define your prompt , add tables and attach images to help AI understand your requirements.

Adding tables

By default, when you click on “Add table”, you will see only the tables from the current app.

This confused me at first, because I thought I cannot pick other tables. But you see, that when we click on “Add data”, this adds a slash in your prompt. You can go there and type the name of other tables too. For instance, adding the Account table, which is not in my app yet.

You can find all the tables in your environment by looking at the tab “Data”

Actually, you don’t need to click the “Add data” button, by typing a slash, you can add a table at any point. The limitation is 6 tables per page.

Page name

On the right side, we can see or edit the page name:

It’s interesting to notice the definition in the sitemap: as an Url, with a pagetype =”control”. Looks like a special type of standalone PCF. The PCF is a special one “MscrmControls.UxAgentControl”, but it seems to be a hidden PCF, because I couldn’t find it in the list of controls in my environment. The refId seems to define the content rendered.

/main.aspx?pagetype=control&controlName=MscrmControls.UxAgentControl&data={“refId”:”c8314661-9407-458b-9952-87ff3dae42c6″}

The generation process

Let’s try a very simple prompt. I know it’s a bad prompt, since I don’t say much, but I found amazing how the agent understood me.

Prompt 1.
Create a responsive page for time tracking using the Time Entry table

I’ve added the TimeEntry table, and started the page generation. That’s it.
During the creation process you see the react code .

The result of this very simple prompt is not bad at all. I haven’t defined in the prompt any of the implemented features like

  • search input
  • commands for create, edit, delete
  • grid sorting, paging
  • column filter.

Generative Page AI guessed it all. It also understood the structure of my table, and is showing all relevant fields. When I choose to add a record, or edit one, it opens the model-driven form in a new tab, and I can introduce the data.

See it in action:

https://youtube.com/watch?v=B0vngNzz0eY%3Fversion%3D3%26rel%3D1%26showsearch%3D0%26showinfo%3D1%26iv_load_policy%3D1%26fs%3D1%26hl%3Den%26autohide%3D2%26wmode%3Dtransparent
Implementing even the column manager, column filter or sorting

Issues with this current implementation

Even if the agent was amazing in guessing, some details are not quite right:

  • after closing the edit form, the grid doesn’t refresh
  • the whole data is loading, and paging is working in memory. That wouldn’t work with a big amount of data.
  • But it filters the data on the backend when I search
  • I need to restrict the data to show only the records for the current user
  • the column filter looks different than the one we are used to in MDA, the calendar looks different
  • the calendar picker for the column filter starts on Monday, while the form calendar picker starts on Sunday
Filter when I search on “test” is made in the backend
When I load the data there is no paging.. everything is loaded in memory, and the paging is just taking the data from there

You can see some of this points only if you have a look to the network protocol or to the code. Don’t trust the agent, without checking.

Some code points

By the way, talking about the code, let’s see a few points (you can skip this in case you are not used to code or react).

You can inspect the whole code here, and inspect the difference between some iterations.

The generated page has 691 lines of code, and it’s using material ui. That explains why it’s looking a little different than the pages we are used to in Power Apps/Model-Driven Apps.

1234567891011121314151617181920212223importReact, { useCallback, useEffect, useMemo, useState } from "react";import{  Box,  Typography,  Paper,  useTheme,  Button,  IconButton,  TextField,  InputAdornment,  Menu,  MenuItem,  ListItemIcon,  CircularProgress,} from "@mui/material";import{ DataGrid, GridColDef, GridRowSelectionModel } from "@mui/x-data-grid";import{  AddRegular,  EditRegular,  DeleteRegular,  MoreHorizontalRegular,  SearchRegular,} from "@fluentui/react-icons";

It understands the webAPI and the formatted values

123456789// Get display value for foreign key (use formatted value if available)functiongetFKDisplayValue<T>(  row: ReadableTableRow<T>,  field: keyof T): string | undefined {  const formattedKey = `${String(field)}@OData.Community.Display.V1.FormattedValue`;  // @ts-ignore  returnrow[formattedKey] || "";}

Formatting dates is made based on browser settings. That explains the difference to the form calendar, where the Power Apps user settings are applied.

123456789101112// Format datefunctionformatDate(val: Date | string | undefined | null): string {  if(!val) return"";  const date = typeofval === "string"? newDate(val) : val;  if(isNaN(date.getTime())) return"";  // Format as yyyy-MM-dd  returndate.toLocaleDateString(undefined, {    year: "numeric",    month: "short",    day: "numeric",  });}
The page calendar starts on Monday, because of my browser settings
The form calendar starts on Sunday

To work with data, is using an internal dataApi:

12345const result = awaitdataApi.queryTable("diana_timeentry", {  select: fieldsToFetch,  pageSize: PAGE_SIZE,  filter,});

To open the form, it doesn’t use the Xrm.Navigation api, but a window.open

12345678910// Toolbar actionsconst handleAdd = () => {  // In Power Apps, this would open the create form (navigate to entity record)  const baseUrl = window.location.origin + window.location.pathname;  window.open(    `${baseUrl}?pagetype=entityrecord&etn=diana_timeentry&id=`,    "_blank",    "noopener"  );};

The grid is implemented with the Material UI DataGrid , which is different than the rest of the model-driven app.

1234567891011121314151617181920import{ DataGrid, GridColDef, GridRowSelectionModel } from "@mui/x-data-grid";///....  <DataGrid          rows={rows}          columns={columns}          getRowId={(row) => row.diana_timeentryid}          pageSizeOptions={[10, 20, 50]}          initialState={{            pagination: {              paginationModel: {                pageSize: 10,              },            },          }}          disableRowSelectionOnClick          checkboxSelection          rowSelectionModel={selectionModel}          onRowSelectionModelChange={onSelectionModelChange}          loading={loading}

But overall you can follow the code very well, it’s commented so you can easily find the sections.

Good to know on editing

The agent is talking with us. After each prompt we see “Agent thoughts”:

You can always learn from the thoughts, and improve your prompt. Because you can never be sure, that next time you’ll use it, you’ll get the same output. Here are complete “thoughts” of the first prompt. I’ve highlighted a few points about what the agent supposed, and what the agent missed during thinking.

//——————-Thoughts start ———————————

1. Requirements

Understanding the User Request

  • The user has requested a responsive page for time tracking using the Time Entry table (diana_timeentry).
  • The user has not specified detailed requirements such as what fields to display, whether to support CRUD operations, or what the layout should look like.

Table Analysis

From the provided type definitions, the diana_timeentry table has these columns:

  • diana_timeentryid (PK, readonly)
  • diana_date (Date)
  • diana_dialog (enum)
  • diana_dummy (string)
  • diana_dummyfx (string, readonly)
  • diana_duration (number)
  • _diana_milestone_value (FK, readonly)
  • diana_milestonegroupingname (string, readonly)
  • diana_name (string)
  • _diana_projectid_value (FK, readonly)
  • diana_trigger (string)
  • diana_value (number)
  • statecode (enum)
  • statuscode (enum)

Requirements List

  • Display a responsive page for tracking time entries.
  • Use the diana_timeentry table for data.
  • List the time entries in a grid (data table) format.
  • Provide a page header with the title “Time tracking”.
  • Allow searching/filtering on key fields (such as name, date, dialog, status).
  • Page actions: Add, Edit, and Delete (if applicable).
  • Show the most relevant columns in the grid (e.g., Name, Date, Duration, Dialog, Project, Milestone, Status).
  • Properly handle overflow and scrolling so only the grid body scrolls.
  • Follow Power Apps styling/layout as shown in MUI sample codes.

2. Assumptions

  • Since the user didn’t specify, I will assume the need is for a single-screen responsive grid with filtering/search and basic CRUD actions.
  • The page will display the most relevant fields for easy time tracking.
  • The Add/Edit/Delete actions will open forms or dialogs (for now, I will wire up the button actions but not implement full forms unless requested).
  • Filtering/search will be on the diana_name field and possibly others if time permits.
  • Enum and FK columns will use their formatted values for display.
  • The user expects a modern business application look and feel.

Additional information that would help:

  • Should the Add/Edit actions open a dialog on this page or navigate to a record form?
  • Should all columns be displayed, or only a subset?
  • Is inline editing in the grid needed, or only through dialogs?
  • Any custom filtering or grouping needed?

3. Major Functions

  • fetchTimeEntries: Query the diana_timeentry table with optional search/filter and update grid state.
  • handleSearchChange: Update the search state and refetch data.
  • handleAdd, handleEdit, handleDelete: Wire up toolbar actions (future: open dialog or navigate).
  • renderPageHeader: Render the page header with title, actions, and search bar.
  • renderTimeEntryGrid: Render the main grid with time entry data and proper column configuration.
  • formatEnumValue: Utility to show formatted enum values.
  • formatFKValue: Utility to show formatted FK display names.

4. Layout, Styling, and Theming

  • Use the reference MUI sample codes for layout and section styling.
  • The main page will have a header section and a grid section, both wrapped in Paper components.
  • Responsive grid sizing, with only the grid body scrolling.
  • All interactive elements have proper hover/focus/active states.
  • All labels/texts use sentence case.
  • Use the theme’s spacing, colors, and typography.

5. Scrollbars and Overflow

  • The grid section will have flex: 1 and minHeight: 0 to ensure only grid body scrolls.
  • DataGrid will have its height set to 100% of its parent, which is sized by the flex layout.
  • The overall page container will use flexDirection: column and fill the parent.

6. Final Verification

  • All requirements are covered.
  • All code follows TypeScript and MUI best practices.
  • All imports are correct.
  • Only supported libraries are used.
  • All subcomponents and functions are included in a single file.
  • No missing features based on the user request.

//——————-Thoughts end————————–

You can save and then publish the page, and see it working in your app too.

And here the agent summary:

Prompt 2:

Prompt 2:
Group the grid data by time entry date

I actually wanted it collapsible, so let’s try a 3rd prompt:

Prompt 3: collapsible groups

Prompt 3:
Make the groups by date collapsible and expandable.

You can see that it added the react functionality, without changing the complete code

Prompt 4: inline editable

Prompt 4:
Make the grid inline editable, by allowing to pick the Projects from the table Project and the milestones from the table Milestone.
Add the projects:

Now the data sources includes all three tables.

And I can pick now projects through the dropdown.

See the demo about this step:

https://youtube.com/watch?v=WDqEPfBm1DQ%3Fversion%3D3%26rel%3D1%26showsearch%3D0%26showinfo%3D1%26iv_load_policy%3D1%26fs%3D1%26hl%3Den%26autohide%3D2%26wmode%3Dtransparent

Notice that the milestone lookup doesn’t work. Also the project doesn’t seem to be saved.

But if you have a look to the network protocol, you see that it’s actually saved, so it’s only a refresh issue.

But if you refresh the data, you see that everything is fine.

Saving and Publishing

You can save the page only once. After that the Save button is disabled. But don’t worry, actually everything is autosaved.

When you’re ready to publish your page, you can use the “publish” button. This publishes only this page, and the users can see the page in the app.

Since you cannot decide when to save, in case you have issues you can use the “Restore” or the “Undo” buttons. The Undo button applies to the last prompt, while Restore can be applied to revert back to older versions.

You can also provide feedback to the product team, using the thumb up/down features. In case you have issues, the thumb down is the best way to provide the error to the team, including the context metadata.

Limitations

I think this feature could change the way we develop. But it’s a very early stage of the public preview, and there are some serious limitations ( you can find all limitations here ). Right now it’s more about getting to know what we can do, how powerful it is, and hope or dream about how this could evolve in the fulness of time.

I find the following the biggest limitations, and hope they are only temporary:

“You can’t edit the generated code” [Edit: this was offered before Nov 2025]

It’s extremely fast to start a page, and implement the features. But it’s getting harder to get very specific. Sometimes the AI understand me, sometimes not at all. Maybe in the fullness of time I’ll learn to talk to the agent (like you know your kids, and know how to approach them), and be fast also when I need to specify details. But as a developer I would be faster if I get the page prompted until it’s almost ready, and then I can just modify the code as needed.

With Custom Pages we could also start very fast, but sometimes we needed some real gymnastics to improve it. And sometimes we just hit some hard limitation, and couldn’t improve much. Or maybe we needed to make some technical PCFs to improve the performance. The Generative Pages would bring this to a new level: we could start verry verry fast, and use the code to get very precise.

Generated pages can’t be exported and imported in other Power Platform with Dataverse environments [Edit, ALM was offered starting with the Nov 2025]

This practically means that we cannot use it in ALM, so probably cannot shift a page developed in dev environment to a production environment. I hope this is a limitation only for now.

Only up to 6 Dataverse tables are supported

We can use the CRUD operations. Actually we can just prompt something like “make this grid editable”.

It’s documented that the tables should be added to the first prompt. It seems to me that we can add them later too, but we can use only Dataverse tables. Not something else. No connectors for now.

I didn’t knew this in the beginning, and prompted something like “Show my outlook appointments in a dropdown”. I haven’t notice what’s happening, and the Copilot seemed to do that, except that they weren’t my appointments. In the end I’ve realized that it was just “hard coded”, “dummy data” added by the prompt. We definitely need to have a deeper look on the resulted code.

Only US English is supported

Well, that is probably a temporary limitation. I’m used to that. I’m more concerned that I’m not a native speaker, and probably my prompts are not always good enough, just because I can’t express better.

You can only access your last 5 iterations with the agent. [Edit: no limitation on iterations anymore]

This one is actually the biggest issue for me, and hopefully only temporary. We are supposed to iterate, add more features, fix issues, sometimes get errors, sometimes the prompt doesn’t produce what we expect and we want to undo and then continue. Sometimes the history stays, but not always. Also, when you reopen the page you have only 5 prompts. And it seems to me that the “Undo” or “Restore” is also a prompt.

It happened quite a few times to loose the page, since, after reopening, I had only the first prompt and the last prompts (which actually were my problems-prompts and restores). Even if the page still implements everything you had before, all the prompts in between were lost, so I couldn’t restore something I could work with.

My recommendation: always save your prompts somewhere else too.

You cannot save a copy of the page

You can only rename the page, or save it, but you don’t have a “save as”. In combination with the autosave, and the “5 prompts” limitation, this one is a hard one too. You cannot make an intermediate copy, just in case the next prompt would fail.

Starting over won’t produce the same output

This is because of the nature of LLM, not a limitation of the Generative Pages. The output is not always the same every time. So if your prompt worked just fine last time, the second time it could make another assumptions. Of course, the more details we provide in the prompt, the result will stay closer to last time. But there is no guarantee to get to the same result.

For instance, I’ve used the same prompts, and got a page where the grid has no paging, no column filter or sorting, but with an inline dialog this time:

Changing during the iterations

If we make more iterations, the agent tries to modify only what’s needed, which is great. But sometimes it breaks features implemented with a previous prompt. So probably is a good practice to test all the features after each prompt, so you can detect early enough if you need to undo a prompt. Otherwise it could get hard to fix it again later.

Works only in Model-Driven Apps

Can be used only as a complete page

The Generated Page can be only used in the site map. Right now we cannot use it as a dialog, and it cannot be embedded on forms or views.

Conculsion

A game changer! A huge step in fusion development, with the potential to make the app development extremely fast, without having to pay the price of the performance. The results are amazing, and it’s getting harder only when it’s about details. The limitations right now are pretty heavy, but I believe that Gen Pages could change the way we develop, as soon it gets more reliable and adds some needed capabilities.

I also think that we need to learn to talk to the agent: what it understands the best, how to fix some typical problems. Or maybe the agent will learn better what we usually expect from it, so it’s assumptions will be closer to what we actually need.

One way or another, I think that the pro-devs are still needed. Because they can detect issues before they happend, because they know what matters in the code being generated, and can talk very precise to the agent, so it cannot be misunderstood. And of course, because the result is react, they can inspect the page and fix problems directly on the source.

Stay tuned

In this post I’ve just showed how to deal with Generative Pages in general. In the next post I’ll show you how I’ve made a page which really integrates in Model-Driven Apps: with a pre-defined design and by using Fluent UI 9. Until then I would love to hear from you, what you think about Generative Pages in Power Apps.

About The Author

Diana Birkelbach

5x Microsoft MVP | PCFLady | Blogger | Dynamics 365 & Power Platform Developer | ORBIS SE

Birkelbach, D (04/02/2026) First Look at Generative Pages in Power Apps.

Share this on...