The three surfaces
Model-driven apps generate their UI from Dataverse metadata. You define tables, forms, views and a sitemap; the platform draws it, and gets security, accessibility, responsiveness and auditing right without you doing anything. For record-centric work — a table, a form, a view — very little else is as productive.
Canvas apps give you pixel control and Power Fx. Excellent for focused task apps; less comfortable when the thing you are building is large, needs real state management, or needs a component that does not exist.
Code apps let you build the front end as a normal web application — React, TypeScript, your own libraries, your own build — and have the Power Platform host, authenticate and govern it. You are writing an application; the platform still handles sign-in through Entra ID, DLP and sharing.
The ceiling people actually hit
It is not that model-driven apps are limited. It is that a whole category of business screen is not a form:
- A week grid for timesheets — projects down the side, days across, editing in cells, totals per day and per week.
- A planboard — people against a calendar, bookings as blocks you click to edit.
- A utilisation matrix — people down, weeks across, each cell a percentage of capacity, coloured by threshold.
- An approval queue mixing record types, with bulk selection and inline editing.
- An editable pivot for forecasting.
You can express these on a model-driven surface, but you are working against the grain. The traditional answer was an embedded custom web resource, and that is where the cost lives — because you end up with several of them.
What the old approach actually costs
We rebuilt a PSA suite that had gone down this road, and the estate looked like this:
- Timesheets, planboard, Gantt and approvals were each a separate embedded app.
- Behind them sat three separate vendored ExtJS trees, none current.
- A full page load happened between the shell and every custom screen.
- Approvals were scattered across three screens, each with its own filters.
- Grid and scheduler components were commercially licensed, per developer.
- Client variations were made by forking, so a fix had to be applied N times and usually was not.
None of that is anyone's incompetence. It is what happens when each non-form screen is solved independently over several years.
What a code app changes
One application with client-side routing — no page reload between screens. One component stack, chosen once. Typed services generated per Dataverse table rather than hand-written fetch calls. And one codebase for every client, with variation as configuration rather than a fork.
That last point is the one that compounds. A fix reaches everyone, or it reaches whoever's branch someone remembered.
What does not change, and should not
The platform still owns the things you do not want to own:
- Sign-in is Entra ID. No token plumbing of yours.
- DLP and sharing are enforced by the platform, not by your bundle.
- Data is the same Dataverse, same tables, same security roles.
- Rules that must not be bypassable stay in plugins. A front end you can read is not a place to enforce a rate table. If your code app is where pricing logic lives, you have moved a business rule somewhere a browser can reach it.
How to choose
| The screen is… | Build it as |
|---|---|
| A record, a form, a view, a lookup | Model-driven. Do not fight this. |
| One focused task, small, quick to ship | Canvas |
| A grid, board, matrix or queue with real interaction | Code app |
| A suite of several of the above | Code app, decisively — this is the case it exists for |
| Reference data maintenance, dozens of tables | Model-driven, or one metadata-driven screen |
The honest test is not sophistication. It is: can the form designer draw this? If yes, let it. Every screen you hand to the platform is a screen you are not maintaining.
The mixed answer is usually right
These are not exclusive. PSA Hub is code-app screens for the parts that need them and model-driven surfaces for the parts that do not — 87 registered entities of reference data sit behind one metadata-driven registry screen rather than 45 hand-built subareas, because adding a reference table should be a config entry and not a sprint.
A pilot group can also move to new screens while everyone else stays on the existing app, because both read the same tables. Cut over per team rather than per tenant.
The costs, stated
- You need front-end engineers. A code app is a real application with a build, dependencies and upgrades. If you have nobody to own that, model-driven is genuinely the better answer for you.
- You inherit accessibility. Model-driven gives you a lot of it free. In a code app it is yours, and it is work.
- Dependencies age. The ExtJS trees above did not start out stale.
- It needs a connection. No offline mode, same as any Power Apps surface.
Which is the argument for buying one rather than building one, if what you need is a category somebody already maintains. That is not a neutral thing for us to say — but it is the same calculation either way, and the maintenance is real whoever does it.