The phrase "low code" has carried a lot of baggage over the years. For a long time it was synonymous with drag-and-drop, formula bars, and the implicit promise that you could build real applications without writing real code. That era is not over β canvas apps and model-driven apps are still excellent tools β but something has shifted, and it is worth naming clearly.
AI has changed what "low code" means.
The new definition is not about writing less code because the platform generates it for you. It is about writing less code because the platform absorbs the infrastructure. Consider what building a serious business application actually requires:
- a secure, governed data layer;
- a way to connect to the hundreds of external systems your organisation already uses;
- authentication and authorisation that your IT department will actually approve;
- server-side logic that keeps secrets off the client;
- APIs that can be called from any frontend;
- lifecycle management across dev, test, and production environments.
None of this is your business problem. All of it is work that stands between you and the thing you are actually trying to build.
This is where the Power Platform has always been something more interesting than its "low code" label suggests. Dataverse gives you an enterprise-grade relational store with row-level security, audit trails, and a rich metadata model β without a DBA. Over 1,500 connectors let your app talk to Salesforce, SharePoint, Azure DevOps, or a payment gateway with a single CLI command and a generated TypeScript service, rather than weeks of OAuth plumbing. Power Automate handles the orchestration layer. The platform enforces your organisation's Data Loss Prevention policies automatically. Custom APIs, server-side logic endpoints, and Dataverse plugins give you escape hatches for anything the platform does not handle natively. In short: the Power Platform takes care of everything that is not your business logic, so that your code can be exclusively about your business logic.
That reframing matters because it changes the profile of developer the platform is now genuinely useful for. It is not just makers and citizen developers anymore. It is professional engineers who want the productivity of a managed platform without surrendering control of what their users actually see and experience.
And that is exactly where Power Apps Code Apps (π ) and Power Pages Single-Page Applications (π΅) enter the picture. Both are answers to the one limitation that has always frustrated developers on the Power Platform: the UI. Canvas apps are powerful but visually constrained. Model-driven apps are consistent but rigid. With Code Apps and Power Pages SPAs, you bring your own React application β your own components, your own design system, your own interaction patterns β and the platform simply becomes the infrastructure layer beneath it.
You write the UI. The platform handles everything else.
Both generate web applications. Both let you write your own frontend. Both sit on Dataverse. And yet they serve fundamentally different purposes, come with different toolchains, and make different trade-offs. If you are evaluating which path to take β or you are a platform architect trying to set a standard for your organisation β this post is for you.
π What are they, exactly?
π Power Apps Code Apps are a code-first way to build apps that run inside Power Apps. You write React (or any framework), scaffold and publish with the npx power-apps CLI (the older pac code command group will be deprecated in a future release β start migrating to the npm-based toolset now), and the Power Apps runtime handles authentication, hosting, and connector proxying. The result feels like a regular web app to your users, but it runs under the Power Apps umbrella β which means it inherits DLP policies, Conditional Access, sharing controls, and the full catalogue of 1,500+ Power Platform connectors.
Note on framework choice: The official tooling, scaffolding templates, generated connector service classes, and AI skills all target React + Vite + TypeScript β that is the well-supported default. The platform does also accommodate plain JavaScript and other frameworks; @wyattdave's guide to plain-JS Code Apps is a practical example of a lighter-weight setup without the TypeScript or Vite overhead.
π΅ Power Pages SPAs are a code-first flavour of Power Pages sites. Instead of using the low-code Power Pages Studio to build pages with drag-and-drop sections and Liquid templates (and -my personal pov- cry desperately when you have to add basic form validations), you upload a compiled static bundle β your React app β which becomes the entire site. The Power Pages runtime hosts it, provides the Dataverse Web API, and handles authentication for external users.
The key insight: Code Apps are portals for your employees. Power Pages SPAs are portals for the world.
βοΈ The differences that actually matter
1. π₯ Audience and authentication
This is the most important distinction, and everything else flows from it.
π Code Apps authenticate users against Microsoft Entra ID. Every user must have a Power Apps Premium licence (or a Developer Plan for testing). The Power Apps host manages the entire auth flow β tokens, consent, refresh β and your code never touches a credential. This is ideal for internal line-of-business applications where your users are already in your tenant.
π΅ Power Pages SPAs are designed for external users who may not have a Microsoft account at all. Power Pages supports Microsoft Entra External ID, LinkedIn, local username/password accounts, and anonymous access. Users pay per-site rather than per-user, which makes the economics work for customer-facing portals with large or unpredictable audiences.
The practical implication: if you need an employee expense portal, use Code Apps. If you need a customer self-service portal or a supplier onboarding site, use Power Pages SPA.
2. π Data access: connectors vs Dataverse-only
This difference surprises many developers coming from a canvas app background.
π Code Apps expose the full Power Platform connector ecosystem to your TypeScript code. When you run npx power-apps add-data-source, the CLI generates fully typed TypeScript service classes for that connector. You get autocomplete, compile-time type checking, and IntelliSense for every table, column, and action. If the connector works in Power Automate or canvas apps, it works in a Code App.
π΅ Power Pages SPAs are Dataverse-only on the data side. You call Dataverse through the Web API (/_api/), and every table your frontend touches must have explicit Table Permissions configured and assigned to Web Roles. There is no CLI command to add a connector. If you need to call an external API β say, a CRM, a payment gateway, or a weather service β you must proxy that call through a Server Logic endpoint or a Cloud Flow, neither of which is as frictionless as simply adding a connector in a Code App.
3. βοΈ Server-side logic
Both platforms have a proper server-side logic story β the difference is one of language, not capability.
π΅ Power Pages SPA has native server-side JavaScript endpoints hosted at /_api/serverlogics/<name>. These run on the server, never expose secrets to the client, can aggregate data across multiple Dataverse tables, and can call external REST APIs or Azure Functions with credentials stored safely. The /add-server-logic skill in the power-platform-skills AI plugin can generate these endpoints from a natural-language description β see the AI-assisted development section below.
π Code Apps have an equivalent in Dataverse Custom APIs β reusable, versioned server-side operations that run inside the Dataverse execution pipeline, can be called from any client, and benefit from all the platform's security and governance guarantees. Writing a Custom API feels very similar to writing a Dataverse plugin: you author the handler in C#, deploy it as a plugin assembly, and register it against the Custom API definition. Power Automate flows are also a valid option for orchestration-heavy logic, and they integrate with Code Apps via the npx power-apps add-flow CLI command.
The practical difference is a language choice: if your team lives in JavaScript and wants the fastest path from idea to server-side endpoint, Power Pages Server Logic wins on friction. If your team is comfortable in C# and wants the tighter Dataverse integration, transaction control, and testability that come with a proper plugin-based Custom API, Code Apps has a fully equivalent answer. Neither approach is inherently superior β it depends on the skills your team already has.
4. π οΈ Developer experience
Both platforms support npm run dev to start a local development server with hot-reload β but the experience they provide is very different.
π For Code Apps, npm run dev starts the app and the Power Apps client library (@microsoft/power-apps) handles the connector auth proxy inline. The local server connects to real Power Platform connectors, acquires tokens, and proxies requests automatically. The result is full hot-reload with real data from production connectors out of the box, no separate process required. (Note: the older pac code run command served the same purpose but will be deprecated in a future release in favour of this npm-based approach.)
π΅ For Power Pages SPAs, npm run dev starts the local Vite (or Angular, or Astro) dev server just as fast β but it serves the frontend only. Calls against /_api/ are not proxied and will fail locally; you are developing with mock data or stub services until you deploy. The only way to test against real Dataverse data is to upload the bundle with pac pages upload-code-site and test in the browser against the live environment.
Both platforms get a fast local feedback loop for UI work. Code Apps go further by giving you real connector data in that same loop, which makes the overall inner development cycle meaningfully faster when data-driven behaviour matters.
5. π° Licensing and cost model
π Code Apps use per-user licensing: Power Apps Premium at $20 per user per month. For internal apps with a known, bounded user base, this is predictable and often already covered by existing Microsoft 365 licensing.
π΅ Power Pages uses per-site, capacity-based licensing billed monthly per site, with separate packs for authenticated and anonymous users:
| User type | Tier 1 price | Pack size |
|---|---|---|
| Authenticated | $200 | 100 users |
| Anonymous | $75 | 500 users |
Volume tiers reduce the per-user cost significantly (Tier 2 authenticated: $75/pack at 10,000+ users). One important nuance: users who already hold a Power Apps Premium or Dynamics 365 enterprise licence do not consume authenticated user capacity β they access the site for free, which can significantly reduce the effective cost for internal-facing Power Pages sites.
For external portals with large or unpredictable audiences the per-site model is a significant advantage over per-user licensing. Conversely, for a small internal tool, the per-user model of Code Apps is typically cheaper.
π Pricing details and tier thresholds can change; always verify with the Power Platform licensing FAQ β Power Pages section.
6. π¦ ALM and source control
Both integrate with standard Power Platform ALM (pac solution). π Code Apps live in a standard npm project. Solution packaging for π΅ Power Pages requires manual Web API queries to find component type values and GUIDs, which is more cumbersome than the Code Apps path.
β¨οΈ CLI command comparison
The table below maps common development tasks to their equivalent CLI commands in each world.
Code Apps CLI toolsets: Microsoft ships two overlapping CLIs for Code Apps. The original
pac codesub-command group (part of PAC CLI) will be deprecated in a future release β new development should target the@microsoft/power-appsnpm package exclusively. Commands in the table below use the current toolset where a confirmed equivalent exists; entries that remain aspac codedo not yet have a documented npm equivalent.
| Task | π Code Apps | π΅ Power Pages SPA |
|---|---|---|
| Authenticate to environment | pac auth create --environment <env-id> |
pac auth create -u <dataverse-url> |
| Create / scaffold project | npx power-apps init --display-name "App Name" |
No equivalent β start from a React template and upload |
| Run locally |
npm run dev (auth proxy + dev server via @microsoft/power-apps client) |
npm run dev (frontend only β /_api/ calls fail locally; deploy to test with real data) |
| Deploy / publish | npm run build && npx power-apps push |
pac pages upload-code-site --rootPath <src> --compiledPath <build> --siteName "Site Name" |
| List apps / sites |
pac code list (no npm equivalent yet)
|
pac pages list |
| Download for editing | n/a (source is local) | pac pages download-code-site --path <dir> --webSiteId <site-guid> |
| Add Dataverse table as data source | npx power-apps add-data-source -a dataverse -t <table-logical-name> |
Configure Table Permissions + Web Roles in Studio or YAML, then pac pages upload
|
| Add a non-tabular connector | npx power-apps add-data-source -a <apiName> -c <connectionId> |
Not supported β proxy via Server Logic or Cloud Flow |
| Add a tabular connector | npx power-apps add-data-source -a <apiName> -c <connectionId> -t <tableName> |
Not supported |
| List tables for a connector |
pac code list-tables -a <apiId> -c <connectionId> (no npm equivalent yet)
|
Not applicable |
| Remove a data source |
pac code delete-data-source -a <apiName> -ds <dataSourceName> (no npm equivalent yet)
|
Remove Table Permission from YAML + re-upload |
| Add server-side logic | Register a Dataverse plugin or npx power-apps add-flow <flowId>
|
Copilot CLI plugin: /add-server-logic (generates JS at /_api/serverlogics/<name>) |
| Add / register a cloud flow |
npx power-apps list-flows β npx power-apps add-flow <flowId>
|
Studio: Set up β Cloud flows β Add cloud flow; invoke via POST /_api/cloudflow/v1.0/trigger/<guid>
|
| Remove a flow | npx power-apps remove-flow <flowId> |
Remove flow registration in Studio |
| Add a Dataverse action or function |
npx power-apps find-dataverse-api β npx power-apps add-dataverse-api <name>
|
Call via Web API directly from client code |
| List connection references |
pac code list-connection-references -s <solutionId> (no npm equivalent yet)
|
Not applicable |
| Package as solution |
pac solution init ... (standard flow) |
pac solution init ... β pac solution add-solution-component -sn <sol> -c <siteId> -ct <componentType>
|
π€ AI-assisted development: the Power Platform Skills marketplace
So far we have talked about CLIs, commands, and architecture. But there is a third layer that deserves its own section, because it changes the day-to-day experience of building on both platforms significantly: Microsoft's official AI plugin marketplace for Power Platform development, hosted at github.com/microsoft/power-platform-skills.
This is a collection of plugins for Claude Code and GitHub Copilot CLI that wrap the entire development lifecycle of both Code Apps and Power Pages SPAs into conversational AI skills. Instead of memorising CLI flags and YAML schemas, you describe what you want in plain language and the agent handles the scaffolding, configuration, and deployment.
π What is it?
The repository is a plugin marketplace. A single quick-install script sets up all plugins and keeps them updated:
# macOS / Linux / Windows (cmd)
curl -fsSL https://raw.githubusercontent.com/microsoft/power-platform-skills/main/scripts/install.js | node
Or install individual plugins manually inside a Claude Code or GitHub Copilot CLI session:
/plugin marketplace add microsoft/power-platform-skills
/plugin install power-pages@power-platform-skills
/plugin install code-apps@power-platform-skills
The installer also takes care of installing pac CLI if it is not already present β a nice quality-of-life touch for developers new to the platform.
π The Code Apps plugin
The code-apps plugin (/plugin install code-apps@power-platform-skills) wraps the entire Code Apps workflow into a set of skills built on React + Vite + TypeScript, deployed via npx power-apps push. The key skills are:
| Skill | What it does |
|---|---|
/create-code-app |
Scaffolds a complete new Code App, wires up the Vite dev server, and deploys it to your environment |
/deploy |
Builds and pushes an existing app with npx power-apps push
|
/list-connections |
Lists available Power Platform connections so you can find the connection IDs you need |
/add-datasource |
Router skill β describes what data you need and picks the right connector skill |
/add-dataverse |
Adds a Dataverse table with fully typed TypeScript models and service classes |
/add-sharepoint |
Adds SharePoint Online connector with generated services |
/add-azuredevops |
Adds Azure DevOps connector |
/add-teams |
Adds Microsoft Teams connector |
/add-excel |
Adds Excel Online (Business) connector |
/add-onedrive |
Adds OneDrive for Business connector |
/add-office365 |
Adds Office 365 Outlook connector |
/add-mcscopilot |
Adds a Copilot Studio agent connector |
/add-connector |
Fallback skill for any connector not covered by a dedicated skill |
One important architectural note the plugin enforces: all data access must go through Power Platform connectors. Direct fetch or axios calls to external APIs do not work at runtime inside the Code Apps sandbox. The plugin actively guards against this pattern and redirects you to the correct connector-based approach.
The plugin also uses npx degit for scaffolding rather than git clone, which is the recommended approach for creating new projects from the official templates.
π΅ The Power Pages plugin
The power-pages plugin is considerably more comprehensive, reflecting the greater complexity of the Power Pages SPA surface area. It provides a broad set of skills covering the entire site lifecycle, supported by 4 specialised sub-agents and 2 bundled MCP servers (Playwright for browser testing and Microsoft Learn for grounded documentation lookups).
| Skill | What it does |
|---|---|
/create-site |
Scaffolds a full site from a React, Vue, Angular, or Astro template; applies design direction; provides live browser preview throughout |
/deploy-site |
Builds and uploads to Power Pages via pac pages upload-code-site; handles common blockers like JS attachment restrictions |
/activate-site |
Provisions the website record and subdomain; polls until the site is live at its public URL |
/test-site |
Runtime-tests a deployed site using a real browser (Playwright); crawls links, captures network traffic, screenshots failures |
/setup-datamodel |
Analyses site requirements and creates Dataverse tables, columns, and relationships; produces a Mermaid ER diagram |
/add-sample-data |
Populates Dataverse tables with realistic test data in dependency order |
/integrate-backend |
Router skill β analyses your business problem and recommends Web API, Server Logic, Cloud Flow, or a combination |
/integrate-webapi |
Full Web API lifecycle: scans codebase for mock data, generates typed API client and CRUD services, configures Table Permissions and site settings YAML |
/add-server-logic |
Creates a Server Logic endpoint (server-side JS at /_api/serverlogics/<name>); grounded in live Microsoft Learn docs via the bundled MCP |
/add-cloud-flow |
Discovers available flows in the environment, registers them with the site, generates client-side invocation code |
/create-webroles |
Generates Web Role YAML files with proper UUIDs; enforces uniqueness constraints |
/setup-auth |
Adds login/logout components and role-based UI patterns; framework-specific (hooks, composables, services) |
/audit-permissions |
Audits table permissions against site code and live Dataverse metadata; produces an HTML report grouped by severity |
/add-seo |
Generates robots.txt, sitemap.xml, and meta tags (Open Graph, Twitter Cards) |
The /integrate-webapi skill deserves a closer look because it demonstrates what this kind of AI assistance can actually do. It does not just generate a data access layer β it scans your existing codebase, identifies components using mock data or placeholder fetch calls, maps them to your Dataverse tables, and then refactors those components to use real API calls. It also generates the Table Permission YAML files needed for deployment and validates column names directly against live Dataverse metadata before writing them. That is a non-trivial amount of work that previously required deep knowledge of Power Pages conventions.
The plugin also includes 4 specialised sub-agents that are spawned automatically during certain skills:
| Agent | Role |
|---|---|
| Data Model Architect | Proposes Dataverse tables and ER diagrams based on your site's code (read-only β proposes, never creates) |
| Web API Integration | Creates typed client, services, and hooks for a specific Dataverse table |
| Table Permissions | Proposes CRUD permissions and Web Role scopes with a visual diagram |
| Web API Settings | Proposes site settings with validated column names queried from live Dataverse |
The "read-only, propose before acting" pattern is consistent across the agents β no schema changes are made until you explicitly approve the proposal. This is the right default for a tool that is touching production infrastructure.
πΊοΈ A recommended end-to-end workflow with the Power Pages plugin
The plugin documentation suggests this sequence, which covers the full lifecycle from zero to a live, secured site:
1. /create-site β Scaffold, design, and build pages with live preview
2. /deploy-site β Upload to Power Pages environment
3. /activate-site β Provision a public URL
4. /setup-datamodel β Create Dataverse tables from site analysis
5. /add-sample-data β Populate tables with test records
6. /integrate-backend β Pick the right backend approach
7. /create-webroles β Define access roles
8. /setup-auth β Add login/logout + role-based UI
9. /audit-permissions β Verify permissions for security issues
10. /add-seo β Search engine optimisation
11. /deploy-site β Push final changes live
12. /test-site β Runtime smoke test on the live URL
Each skill is independent and checks its own prerequisites, so you do not need to follow this exact order.
π Comparing the AI skill coverage side-by-side
| Capability | π Code Apps plugin | π΅ Power Pages plugin |
|---|---|---|
| Scaffold new project | /create-code-app |
/create-site |
| Deploy to environment | /deploy |
/deploy-site |
| Activate / go live | Automatic on push |
/activate-site (separate step) |
| Add Dataverse table | /add-dataverse |
/setup-datamodel + /integrate-webapi
|
| Add connector / data source |
/add-datasource (routes to 9 connector skills) |
Not applicable β Dataverse only |
| Server-side logic | No dedicated skill β use flows or Dataverse plugins | /add-server-logic |
| Cloud flow integration |
/add-datasource β add-flow (npm CLI) |
/add-cloud-flow |
| Authentication setup | Automatic (Entra ID via host) | /setup-auth |
| Access control | Automatic (Entra ID roles + DLP) |
/create-webroles + /audit-permissions
|
| Data modelling | Via connector skills |
/setup-datamodel with ER diagram |
| Sample data | Not included | /add-sample-data |
| Runtime testing | Not included |
/test-site (Playwright-based) |
| SEO | Not applicable (internal app) | /add-seo |
The difference in skill depth reflects the difference in platform complexity. Code Apps offload a lot of concerns β auth, hosting, connector wiring β to the platform runtime, so there is less for a developer to configure. Power Pages SPAs require explicit configuration of permissions, roles, auth providers, and site settings, which is why the plugin is considerably more capable in those areas.
π― When to choose which
π Choose Code Apps when:
- Your users are internal employees in your Entra ID tenant
- You need connectors beyond Dataverse (SharePoint, SQL, O365, Salesforce, etc.)
- Developer experience and iteration speed matter β the local dev loop is a genuine differentiator
- You are comfortable with per-user licensing
- You want to leverage existing Power Platform ALM tooling without friction
π΅ Choose Power Pages SPA when:
- You are building a customer, partner, or supplier-facing portal
- You need anonymous or social login (Microsoft Entra External ID, LinkedIn, local accounts)
- You want native server-side logic without standing up Azure Functions
- Your audience is large, external, and better served by per-site pricing
- You are already using Power Pages and want to replace a Liquid/template-based site with a modern SPA
When you might use both:
A common enterprise pattern is to use a Power Pages SPA as the external-facing portal β customer onboarding, case submission, partner collaboration β while Code Apps serve the internal team working those cases. They share the same Dataverse environment and the same data, but serve entirely different audiences with the appropriate auth model for each.
π§© But wait β what about Model-Driven Apps?
Code Apps are not a replacement for Model-Driven Apps. They solve different problems, and picking the wrong one is a common mistake.
Choose a Model-Driven App when:
- Your app is fundamentally about managing Dataverse records β creating, editing, reviewing, approving
- You want built-in platform capabilities for free: business rules, business process flows, dashboards, charts, activity feeds, duplicate detection, auditing
- Your users are comfortable with (or have no strong opinion about) the standard Dataverse forms-and-views paradigm
- Speed of delivery matters more than a bespoke UI β a model-driven app with well-designed forms can be production-ready in days
Choose a Code App instead when:
- You need a completely custom UI/UX that does not map to the forms-and-views model β complex layouts, unconventional interactions, rich visualisations, or a branded design system
- Your users need a consumer-grade experience and the standard model-driven chrome feels too enterprise-heavy
- Your app is not record-centric β it aggregates data across multiple systems, runs a wizard-style flow, or presents dashboards built in React rather than the platform's charting engine
- You need connectors to non-Dataverse data sources in the same experience
The practical heuristic: start with a Model-Driven App. If you find yourself fighting the platform to make a form look or behave the way you need, that is the signal to reach for a Code App. If you can get the job done with forms and views β possibly with PCF controls for specific fields β stay in the Model-Driven world and ship faster.
π Closing thoughts
The Power Platform is increasingly a platform for professional developers, not just low-code makers. Code Apps and Power Pages SPAs are both evidence of that shift: Microsoft is giving developers the tools to write real code while staying inside the managed, governed, connector-rich environment of Power Platform.
The choice between them is not about technical capability β both can produce excellent applications. It is about who your users are and what data they need. Get that analysis right, and the rest of the architecture follows naturally.
Top comments (0)