Skip to content

PCF vs Custom Pages vs Generative Pages vs Code Apps: Choosing the Model-Driven App Custom UI Layer in 2026

A decision framework for the model-driven app custom UI layer in 2026: PCF, custom pages, generative pages, and code apps compared on ALM, licensing, and risk.

Alex Pechenizkiy 14 min read
PCF vs Custom Pages vs Generative Pages vs Code Apps: Choosing the Model-Driven App Custom UI Layer in 2026

Nobody chooses a UI layer for model-driven apps. Teams default to whatever they built last time. The shop that shipped its first PCF control writes PCF controls forever. The team that discovered custom pages builds every wizard as a custom page, including the ones that should have been a form.

In 2026 that habit is an architecture risk, because the model-driven app custom UI question now has four genuinely different answers: PCF controls, custom pages, generative pages, and Power Apps code apps. They sit at different maturity levels, carry different licensing implications, and have wildly different ALM stories. A wrong default does not fail at build time. It fails downstream, as a blocked pipeline, a licensing surprise at renewal, or a preview feature running in production with no SLA behind it.

The fix is not taste. It is a framework: know what each layer is for, demand the ALM path before committing, treat preview status as a first-class architectural input, and expect AI-assisted building to keep moving the trade-offs under your feet. One housekeeping note before anything else: status claims in this article reflect the linked Microsoft Learn pages as of early 2026. Verify every one of them against the Power Platform release planner on the day you decide, because status moves faster than architecture documents do.

What Each Model-Driven App Custom UI Layer Is Actually For

Each layer gets the same treatment: sweet spot, hard limits, and the scenario where picking it is a mistake.

PCF controls: pro-dev power at field and dataset level

Real PCF best practices start with an uncomfortable admission: the Power Apps component framework docs make PCF look like a component library exercise. Write a TypeScript control, declare a manifest, ship it. In production, the component is the easy part. The hard parts are dataset paging, the control lifecycle, and the gap between the local test harness and real form behavior.

Take those one at a time, because each one eats a sprint when it surprises you. Dataset controls receive data in pages governed by platform settings, not by your control, so a grid that assumes it holds the full result set renders a lie. Your control has to respect the paging contract and request the next page explicitly. The lifecycle is the second trap: updateView fires more often than intuition suggests, on data changes, on resize, on context changes you did not cause, and a control that does expensive work on every call will drag the whole form. The third trap is the harness itself. It runs your control against mock data with no form scripting, no business rules, and no competing OnLoad logic. A control that behaves perfectly there can misbehave on a real form where visibility rules and scripts fight for the same lifecycle.

One build-time decision deserves more attention than it usually gets: standard versus virtual controls. Virtual controls render through platform-provided React and Fluent libraries instead of bundling their own, which cuts bundle size and per-instance memory on forms that host many controls. The trade is a different supported API surface, and the docs list which capabilities each hosting surface supports rather than guaranteeing parity. Check that list against your requirements before scaffolding, not after.

The sweet spot is unambiguous: field-level and dataset-level controls that get reused across apps and tables. A date picker with fiscal-period logic, a grid with inline editing semantics your users demand, a visualization bound to a view. Full styling control, native solution packaging, and reuse that compounds. The cost is a pro-dev tax: TypeScript, build tooling, versioned deployments, and a developer who understands the lifecycle. Paying that tax for a control used on one form in one app is the mistake scenario.

Verdict: PCF is the right layer when the control is reusable and the interaction is field or dataset shaped. It is the wrong layer for one-off page experiences.

Custom pages: canvas authoring inside the shell

Custom pages bring canvas authoring into the model-driven shell as a first-class page type, documented in the custom page overview. The sweet spot is the one-off experience: a guided wizard, a dashboard that stitches three data sources, a dialog that replaces a clunky multi-form process. A maker ships it fast, it travels inside the app’s solution, and it opens as a page, a dialog, or a pane.

The hard limits are the canvas limits. Responsive behavior requires deliberate container work and still fights you at odd breakpoints. Power Fx delegation limits apply to every query the page makes. And the maker-vs-developer handoff is real: a page built fast by a maker becomes a maintenance liability when a pro-dev team inherits a wall of nested formulas whose source-control story is an unpacked artifact with noisy diffs.

The mistake scenario is using custom pages as a reuse mechanism. Copying a wizard page into five apps creates five divergent artifacts, each drifting independently.

Verdict: custom pages are the right layer for one-off experiences inside a single app. They are the wrong layer for anything you intend to reuse.

Generative pages: speed up, ceiling unchanged

Generative pages invert the authoring model: describe the page, and AI generates it inside your app. As I write this in early 2026, the generative pages documentation lists the feature as preview, and preview status is the single most important fact about it. The docs describe the generated output as a code-backed page, but how that artifact packages, diffs, and promotes in your tenant is a thing to test, not assume. Preview behavior shifts between release waves, and this feature is moving fast enough that the release planner, not memory, is the source of truth.

The trade is speed for control. Generation collapses the time to a working first version of a simple page. What it does not do is raise the control ceiling. The platform constraints on what a generated page can be are the constraints of the page model itself. Expecting PCF-grade control over rendering, lifecycle, or data access from a generated page is the mistake scenario, and it is a common one, because the demo always looks limitless.

Verdict: generative pages are the right layer for simple internal pages where speed dominates and preview risk is acceptable. They are the wrong layer anywhere control or support guarantees matter.

Power Apps code apps: code-first, and honest about the boundary

Code apps are code-first web apps that run on Power Platform with authentication, hosting, and Dataverse connectivity handled by the platform. The documented developer loop is deliberately familiar: a standard React project scaffolded with Vite, the pac CLI to initialize and push the app, and the Power Apps SDK handling platform auth and connector access so you are not hand-rolling token flows. For a team that already lives in a JavaScript toolchain, the pitch is that the platform meets them where they are instead of asking them to learn Power Fx. Verify the current SDK surface and connector coverage before committing, because the docs list code apps as preview as of this writing and the supported surface is still widening.

Be precise about the architectural boundary, because most teams are not: a code app does not embed inside the model-driven shell. It is a sibling application that shares the environment, the Dataverse database, and the identity model. Integration with a model-driven app happens by navigation, URL, and shared data, not by composition. If your requirement is “a richer experience inside this model-driven app,” a code app is architecturally the wrong shape, however attractive the React story is. And because it talks straight to Dataverse, your Dataverse security design is load-bearing from day one, not a hardening task for later.

The mistake scenario is the expensive one: rebuilding in a code app what a form plus one or two PCF controls would deliver cheaper, with better ALM and zero preview risk. Teams reach for full custom apps because forms feel constraining, when the actual constraint is one grid.

Verdict: code apps are the right layer when the application has genuinely outgrown the form model across most of its screens. They are the wrong layer for patching one weak screen.

The ALM Story Is Where the Decision Gets Made

Here is the position worth defending in any review board: if you cannot articulate the ALM path for a layer, you have not chosen it. It chose you.

Layer
PCF controls
Packaging
Solution component with a versioned control manifest
Source control
TypeScript source in your repo; built artifact ships in the solution
CI/CD maturity
Mature: pac CLI plus standard Azure DevOps and GitHub tasks
Promotion friction
Low, but control version must be bumped for updates to take effect
Layer
Custom pages
Packaging
Ships inside the app and solution as a canvas artifact
Source control
Unpack via pac CLI, or Power Platform Git integration (itself preview); diffs remain noisy either way
CI/CD maturity
Moderate: pipelines handle them, reviewing changes is the weak point
Promotion friction
Moderate: connection references and environment variables need per-environment mapping
Layer
Generative pages
Packaging
Generated output lands as a page inside the solution
Source control
Unproven for generated artifacts; treat as a per-tenant test, not a given
CI/CD maturity
Preview: pipeline behavior is not something to assume
Promotion friction
High until GA; test export and import end to end before relying on it
Layer
Code apps
Packaging
App code lives in your repo; deployed via pac CLI
Source control
Strongest Git fit on paper, since the app is a plain codebase; solution packaging and promotion are the parts to validate while preview
CI/CD maturity
You build it: standard JavaScript pipeline plus Power Platform deploy steps
Promotion friction
Preview tooling evolving; validate the full promotion path in your tenant

Two things the table cannot fully carry.

First, connection references and environment variables are where promotion actually breaks, across all four layers. PCF controls mostly dodge this because they ride the form’s Dataverse context, though a control that calls external services reintroduces the problem through configuration. Custom pages carry connection references for every connector they touch, and those must be mapped per environment or your pipeline stalls at import. Generative pages inherit the custom-page story plus preview uncertainty. Code apps handle connections in code and deployment configuration, which means the mapping discipline is yours to design rather than the platform’s to impose. Whatever layer you pick, rehearse the dev-to-test promotion with real connection references before the first sprint ends.

Second, solution-aware does not mean pipeline-friendly. A component can package cleanly into a solution and still behave differently on export than the docs lead you to expect: ordering quirks, environment-specific settings that travel when you assumed they would not, or the reverse. Rather than claim failure rates, here is the test that matters: export from dev, import to a clean test environment, run the app, then do it again through your actual pipeline. If any layer fails that drill, you have your answer before writing feature code. The same logic behind PCF best practices applies to all four: version deliberately, build from source, and never treat the dev environment as the artifact of record. If you want the broader discipline, the approach in turning ADO work items into specs any AI agent can build from is the same muscle applied upstream.

The takeaway: run the ALM drill before the architecture decision is final, not after.

Licensing and Supportability: The Questions Architects Skip

I will not reproduce a pricing table, because the Power Platform licensing guide changes and interpretation is where architects get burned. What stays stable is the shape of the entitlement question per layer, so ask it per layer.

For PCF, the baseline question is whether the users’ existing entitlement, whatever covers the model-driven app today, already covers the control. A PCF control does not add a SKU by itself, but a control that calls external services pulls those services’ licensing and governance into scope through the back door.

For custom pages, the question is connectors. The licensing guide treats premium connectors as a property of the app, so ask what a single premium connector on one page does to the license requirement for every user of the whole app, and quote the guide’s current language rather than interpreting it.

For generative pages, the question is prerequisites. Preview features are not described in the licensing guide the way GA features are, so ask what Copilot, AI capacity, or managed environment requirements apply in your tenant right now, and get the answer in writing from the current docs rather than from a demo.

For code apps, the question is what the entitlement looks like at GA and what the app consumes. A chatty React app talking straight to Dataverse burns API request entitlements differently than a form does, and Dataverse storage and API consumption are the hidden cost drivers that surface at renewal, not at design review.

Then the question that overrides the other four: is the layer GA, and if not, what is the written plan for when preview terms bite?

An illustrative example, and only illustrative: a generative page that saves two weeks of build effort but requires a rebuild as a custom page at GA because the generated artifact does not migrate cleanly has negative total cost. Illustrative numbers only; calibrate against your own tenant and agreements, actuals vary. The point survives the caveat: the cheapest layer to build is often not the cheapest layer to license or support.

The Decision Matrix

These are representative scenarios, not projects anyone ran. The reasoning column matters more than the verdict column, because your scenario will differ in the details and the reasoning is what transfers.

Scenario
Reusable grid or input control used across many apps
Right layer
PCF
Runner-up
None worth naming
Why
Reuse amortizes the pro-dev tax; every other option multiplies divergent copies
Scenario
One-off wizard or guided experience in one app
Right layer
Custom page
Runner-up
PCF dialog
Why
Canvas authoring ships fastest and lives inside the app solution; PCF only wins if the interaction is dataset shaped
Scenario
Polished customer-facing or external UI
Right layer
None of the four
Runner-up
Power Pages or a standalone web app
Why
External users do not belong in the model-driven shell; licensing and identity both fight you, so stop forcing it
Scenario
Quick internal admin page, low stakes
Right layer
Generative page
Runner-up
Custom page
Why
Speed wins when the control ceiling is irrelevant; preview status keeps it out of anything tier one
Scenario
Complex app that outgrew the form model
Right layer
Code app
Runner-up
Standalone React app on Azure
Why
When forms fight you on every screen, stop paying the workaround tax; accept preview risk consciously or take the Azure path

Note the third row. A real decision framework includes the option of choosing none of the four. Model-driven custom UI layers exist to serve model-driven users. The moment the audience is external, the honest architecture conversation leaves this article’s scope, and pretending otherwise is how customer portals end up welded to internal app shells.

The takeaway: pick per scenario, write the reasoning down, and make the next team argue against the reasoning rather than the verdict.

How AI-Assisted Building Reshuffles the Deck

The old heuristic was simple: pro-code layers cost more to build, so you reserved them for high-value scenarios. AI-assisted development breaks that heuristic from both ends.

On the high-control end, Copilot and code-generation tooling compress the PCF and code-app tax hard. The TypeScript scaffolding, the manifest boilerplate, the React plumbing: that is exactly the work AI generates well, because it is pattern-dense and lives in toolchains the models know. The effort gap between “maker builds a custom page” and “developer ships a PCF control” narrows every release wave. The strategic question has already migrated from “can we afford custom code?” to “can we govern and maintain AI-accelerated custom code?” That is a governance problem, and it rhymes with every other one on this platform, including why tool shape is your new security perimeter once agents start touching Dataverse directly.

On the low-control end, generative pages collapse the effort floor for simple cases. What AI does not do, and will not do, is raise the generative-page control ceiling. That ceiling is a platform constraint, not a skills constraint, and no amount of prompt quality moves it.

Both forces press inward on the same layer: custom pages. Simple custom pages lose to generation on speed. Complex custom pages lose to AI-accelerated pro-code on control, maintainability, and source-control story. Custom pages keep a real niche in 2026, the one-off wizard inside a single app, but it is a shrinking niche, and your 2027 roadmap should assume the squeeze continues rather than reverses.

Which is why the habit problem compounds. The UI layer you pick by muscle memory is a decision someone else pays for, in ALM pain, in a licensing clause nobody read, or in a preview feature holding up a production process with no SLA underneath it. And the next habit is already forming: teams are starting to default to whatever their AI tooling generates fastest. That default will age exactly as well as the last one. The constraint that decides your 2027 architecture is no longer what your team can build. It is what your organization can govern.

Want this for your Dynamics 365 app?

Tell me which screens or steps slow your team down. I read every message and reply personally with what I would fix first, and whether it is a maker fix, a custom page, or a PCF control.

Stay in the loop

Get new posts delivered to your inbox. No spam, unsubscribe anytime.

Related articles