Skip to main content

app-builder

Builds and edits a model-driven Power Apps app from a natural-language intent — tables, columns, relationships, adaptive forms with sub-grids, views, Choice-column charts, business rules, business process flows, generative page intents for overview/dashboard surfaces (page `.tsx` generated in generate-pages after plan approval), and an app module + sitemap — via the headless cds-maker-sdk. Runs an interactive, multi-turn authoring flow (env selection, jobs-to-be-done first, then design-only App Spec authoring across confirmed levels, guardrail lint, plan-mode approval, generate-pages, full build) and a narrated build, and can download a deployed app back into an editable spec to change it. Use when the user says "build an app for X", "create a model-driven app", "make me an app to manage Y", "add a business process flow", or "edit/add to my app". This skill stands alone and does not require /genpage — but for a standalone generative page added to an app that already exists, use /genpage instead.

Source facts

Repository
microsoft/power-platform-skills
Last source activity
October 1, 2026 at 20:57
Detected SKILL.md language
English
Stars
969
Forks
196

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
app-builder
version
1.0.0
description
Builds and edits a model-driven Power Apps app from a natural-language intent — tables, columns, relationships, adaptive forms with sub-grids, views, Choice-column charts, business rules, business process flows, generative page intents for overview/dashboard surfaces (page `.tsx` generated in generate-pages after plan approval), and an app module + sitemap — via the headless cds-maker-sdk. Runs an interactive, multi-turn authoring flow (env selection, jobs-to-be-done first, then design-only App Spec authoring across confirmed levels, guardrail lint, plan-mode approval, generate-pages, full build) and a narrated build, and can download a deployed app back into an editable spec to change it. Use when the user says "build an app for X", "create a model-driven app", "make me an app to manage Y", "add a business process flow", or "edit/add to my app". This skill stands alone and does not require /genpage — but for a standalone generative page added to an app that already exists, use /genpage instead.
author
Microsoft Corporation
argument-hint
<app description>
user-invocable
true
allowed-tools
Read, Write, Edit, Bash, Glob, Grep, Task, AskUserQuestion, EnterPlanMode, ExitPlanMode, TaskCreate, TaskUpdate, TaskList, read, edit, execute, search, agent, todo
> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding. # app-builder — intent → model-driven app Turn a natural-language intent into a deployed model-driven app. You author a reviewable **App Spec** (JSON) with the user across confirmed turns, then a deterministic engine (`cds-maker-sdk`, vendored) builds it — tables/columns/relationships, sample data, views, Choice-column charts, adaptive forms with sub-grids, **generative pages** for overview/dashboard surfaces, and the app module + sitemap. The **same spec drives create and edit**: download a deployed app back into a spec, change it, and re-run the build (it's idempotent). ## CRITICAL — run the interactive flow in THIS conversation (the main loop) > **You MUST run the authoring questions and the build narration yourself, in the main > conversation. Do NOT dispatch a subagent (`Task`) for the interactive steps.** > > A subagent is headless — `AskUserQuestion` and plan mode do not reach the user from inside one > (its only output is its final message). The whole point of this skill is the multi-turn, > propose-then-confirm experience, so every `AskUserQuestion`, `EnterPlanMode`, and live build > status line must originate here, in the main loop. ## CRITICAL — the user sees your chat message, NOT tool output > **Shell/tool output — the result of running `preview-app.js`, a dry-run plan, or a lint — > is COLLAPSED BY DEFAULT in the UI. The user does NOT see it unless they manually expand the > tool panel.** Running the command is therefore NOT the same as showing the user. Every artifact > the user must **read, review, or approve** — the whole-app **preview wireframes**, the > **dry-run build plan**, and blocking **lint findings** — MUST be reproduced **verbatim in your > chat reply**, inside a fenced ` ``` ` code block. Never say "the preview looks right" and leave > the content buried in a collapsed panel: **paste it into your message.** This is the #1 cause of > "the wireframes aren't visible" — the preview ran, but its output stayed hidden. ## Capabilities — the full toolbox (pick best-fit per requirement) You are a **complete** model-driven app builder, not a single-surface tool. Everything below ships in one App Spec and one build — choose what best serves the user's requirement to make a **useful, prod-ready** app; don't under-build (a bare table list) or over-build (surfaces nobody asked for): - **Data model** — tables (give each custom table a **meaningful Fluent-style SVG table icon by default**; propose what the glyph will **depict** in words — never a Fluent token name — and record it as `iconDescription` before drawing the SVG — see [`references/authoring-flow.md`](../../references/authoring-flow.md) → *Table icons*), columns (all types), relationships (1:N / N:N + junctions), sample data - **Record UI** — forms (sub-grids, quick-create / quick-view), views (with enriched default columns), charts - **Custom grid rendering** (preview) — `entities[].columns[].visualization`: render a column as a `RadialDial`, `LineChart`, `HeatMap` or `StarRating` in **every** grid and view that shows it, instead of plain text. Reach for it when a column is a *magnitude a user scans* (a score, a utilization %, a rating, a priority) rather than a value they read exactly — it makes a list scannable at a glance for one line of spec. It is per-*column*, so set it once on the column, not on each view. **Preview:** on an environment where it is not provisioned the build skips it and everything else still deploys, so it is always safe to include. - **Actions** — modern command-bar buttons (incl. flyout / split menus), web resources (form JS / HTML / CSS). Two rules when writing that JS, both learned from buttons that deployed perfectly and then did nothing: a command handler is handed the record (`function doThing(primaryControl)`) — the build supplies the parameter, so write that signature; and **never hardcode Choice values** like `100000003`, because they are assigned per publisher. Resolve by label via `getOptions()` (see `references/app-spec-schema-advanced.md` → webResources). Note also that command and web-resource **edits do not redeploy on rebuild** — the phases reuse what exists, so changing a button or a script means deleting it first. - **Form logic without code** — `businessRules[]`: show/hide, lock/unlock, set-required and set-value, driven by a condition on the record. **Reach for a business rule before form JS** when the requirement is field-level and declarative — it is visible in the maker, survives solution export, and needs no web resource. Use form JS when the logic needs a real API call, cross-record work, or anything beyond the four supported actions. - **Guided processes** — `businessProcessFlows[]`: the staged bar across the top of a record (ordered stages, each with steps bound to that table's columns — **every step must bind a `field`**; the platform rejects one without, so use a Boolean flag for a manual check-off). Reach for one when the user describes work moving through **phases** — "triage → investigate → resolve", "lead → qualify → close". Author it `Active` (the default) or the stage bar does not appear at all. Who may run a flow is `securityRoles: { "personas": [...] }` (Active flows only). v1 is single-entity and linear: cross-entity stages, branching and stage actions are **rejected** by the spec gate, so offer Maker for those rather than writing them into the spec. - **Surfaces** — **generative pages** (modern dashboards / overviews / analytics / landing — the default), classic dashboards (opt-in), external URLs - **App shell** — the app module + sitemap, with per-subarea icons. Turn on the **modern shell** with `app.newLook: true` unless the user asks for the classic one; it is opt-in and best-effort, so a tenant without the setting still gets a working app. `app.headerNavigationRefresh` controls the **Wave 2 header/navigation refresh** — a *separate, independent* setting whose platform default is **ON**, so set it to `false` only when the user explicitly wants the classic header. - **Security & access** — one **security role per persona**, sized from that persona's jobs-to-be-done (the entity access each job needs, unioned into the role), so the app **opens for non-admins**. - **AI-first features** (admin-gated) — form-fill assist, natural-language grid/view search, NL chart / AI data visualization, M365 Copilot (opt-in); per-table Copilot row summaries (Insight Cards) with tailored prompts, auto-selected for good-candidate tables Author the **smallest spec that fully satisfies the ask**, then let the user refine. The **Genpage-first policy** below is the record-vs-dashboard rule; [`references/app-spec-schema.md`](../../references/app-spec-schema.md) documents every field. ## Genpage-first policy (surface classification) Every app surface is one of two kinds — **enumerate the app's surfaces and classify each one**, don't decide page-by-page in passing: - **Record surfaces** (create/read/update/list a table's rows) → a **model-driven form + view**. - **Everything else** — overview/landing, dashboard, KPIs, analytics, guided or wizard flow, composite or comparison screen → a **generative page** (`pages[]`), **not** a classic dashboard. Rules: - An app whose jobs include an overview, a queue, analytics or a guided flow but which proposes no pages has missed a surface. Where the app genuinely is record-CRUD only, say so explicitly rather than silently omitting pages. - A **traditional `dashboards[]`** is emitted **only on explicit request** (e.g. "use a classic dashboard"). - A generative page is authored as a **design intent** (`source: { kind: "intent" }`, `schemaVersion: 2`) during Phase 1 — its `.tsx` is written in **Phase 1.5 — Generate pages**, after plan approval and after the data pre-build creates the tables so `pac model genpage generate-types` can emit `RuntimeTypes.ts`. See [`references/authoring-flow.md`](../../references/authoring-flow.md) → *Pages*. - The build's `pages` phase uploads each page via `pac model genpage upload` **without `--add-to-sitemap`** — the SDK is the **single sitemap writer**, so a page's nav entry comes from a `page` subarea in `appShell` (referenced by the page's **`key`**). See [`references/app-spec-schema.md`](../../references/app-spec-schema.md) → `pages[]`. - **Every page in `pages[]` must be sitemap-placed** — validation rejects any page absent from the sitemap. A "detail" page that receives a caller-supplied id is a normal sitemap page; it reads its input via `pageInput?.data?.<field>`. Navigation-only (headless) pages are not supported. - **A page that declares `pageInput` MUST declare `directEntry`.** Because every page is sitemap-placed, a detail page is also reachable straight from the app navigation with **no input** — a state a user reaches by clicking. Say what happens then: `{ "behavior": "selector" }` shows a picker and then the record, `{ "behavior": "emptyState" }` explains and renders nothing broken. Prefer `selector` when the table is browsable; it is the more useful landing. Every key in `pageInput.data` must also be produced by some page's `navigatesTo[].data`, or the generated page reads a key nothing ever sets. - **Three-authority page identity** (build + download + verify all follow this): (1) **IDENTITY** — the durable `<app>_pagemanifest` (`key → pageId`); a downloaded spec's own `pages[].pageId` outranks it for that rebuild. (2) **EXISTENCE** — env-wide `pac model genpage list` (crash-safe; decides create-vs-reuse). (3) **MEMBERSHIP** — the app's sitemap `GenPageId` set (placement, download enumeration, verify coverage). All matching is by id — never by display name. - **Multi-page navigation uses `PAGEREF_<key>`** (the stable `pages[].key`) as the `pageId` placeholder; the build resolves each to the real page GUID in a run-scoped staging copy (the canonical `.tsx` is never mutated), then **verifies every nav edge** resolves to the deployed page's `GenPageId`. ## Workflow ### Phase 0 — Working directory 1. Derive a short kebab-case slug from `$ARGUMENTS` (e.g. "Project Tracker" → `project-tracker`). 2. Create the directory (`mkdir -p <slug>` on bash/PowerShell; `mkdir <slug>` on cmd, which has no `-p` and errors if it exists) and resolve its absolute path. It holds `app-spec.json`, `model-app-plan.md`, and `workflow-log.md`. ### Phase 1 — Author the App Spec (interactive, main loop) Follow **[references/authoring-flow.md](../../references/authoring-flow.md)** step by step, running every prompt yourself via `AskUserQuestion`. In short: #### Unattended runs (Copilot autopilot / automation) Resolve the interaction mode once before the first authoring question: ```bash node "${PLUGIN_ROOT}/scripts/resolve-interaction-mode.js" [--non-interactive] ``` `POWER_PLATFORM_SKILLS_NONINTERACTIVE=1`/`true` (or the equivalent `--non-interactive` invocation) means there is no user waiting for prompts. In that mode: - do not call `AskUserQuestion`, `EnterPlanMode`, or `ExitPlanMode`; - use explicit requirements and supplied existing specs as authoritative, and use documented defaults only where the request leaves a non-destructive choice open; - record each skipped gate as `Unattended default: <question> → <answer> (<reason>)` in `workflow-log.md`; - treat the completed lint, preview, rendered design document, and build dry-run as the approved plan, then continue with the normal approved path; - halt rather than guessing an ambiguous environment, app identity, or destructive structural edit. Suppressing interaction never authorizes destructive work. An unattended existing-app apply still requires `--allow-destructive` wherever the build normally requires it; `POWER_PLATFORM_SKILLS_NONINTERACTIVE` only suppresses prompts. Carry the resolved mode through subprocesses: when it is unattended, append `--non-interactive` to every `build-model-app.js` invocation (dry-run, data pre-build, recovery rerun, and full apply). Do not append `--allow-destructive` unless destructive authority was supplied independently. 1. **Prereqs** — `node --version`, `pac help` (> 2.10.0, the plugin's minimum). 2. **Environment (PAC)** — `pac auth list`. If exactly one / an active profile, **confirm it (FYI), don't ask**. If several and none active, **ask** which to use. If none, ask the user to `pac auth create`. Capture the org URL (`pac org who`). 3. **Detect existing** — `pac model list-tables --search …` (exact-match) and `pac model list` to find tables/apps already present; build *around* them. 4. **Levelled authoring** — **first read the App Spec format** so you author to the exact shape (do this once; don't go spelunking through scripts): [`references/app-spec-schema.md`](../../references/app-spec-schema.md) and the worked sample [`samples/app-spec.support-desk.json`](../../samples/app-spec.support-desk.json). That document covers everything an app always has. **Additionally read [`references/app-spec-schema-advanced.md`](../../references/app-spec-schema-advanced.md) once your design calls for a conditional feature** — business rules, a business process flow, command-bar buttons, web resources, global choices, classic dashboards, or `roleGrants[]`. Its pointer table is in the main schema, and the toolbox above tells you when to reach for each; read the detail only for the ones you are actually using, and never skip a capability just to avoid the read. Phase 1 is **design-only**: never emit page `.tsx` here. Each level is confirmed via `AskUserQuestion` before the next begins, and `app-spec.json` is persisted after each — full prompts in the playbook. - **Level (a0) — personas & jobs-to-be-done** (`personas[]`): **before proposing any tables**, ask who will use the app and what each needs to get done. Jobs drive everything that follows — a table exists because a job needs its data, a surface because a job needs to act on it. Deriving the data model first reliably misses surfaces. Privileges come later; capture the jobs now. - **Level (a) — data model**: entities/columns/relationships **derived from those jobs**; run the **early data-model lint** (catches e.g. the relationship-vs-lookup collision before forms are authored on top). - **Descriptions are part of authoring, not a cleanup pass.** Every table, column, view, chart, form, dashboard and business rule takes an optional `description`, and you should **write one as you create the artifact** — never as a backfill. A name says what a thing is called; a description says what it is *for*, and it is the only intent an app carries that an agent can read back later when it inspects an app it did not build. Describe the purpose, not the shape: `"Severity 1-5; drives the escalation rule and the SLA clock"`, not `"The priority column"`. (`commands[]` and `Customer` columns accept one but the SDK cannot write it — you'll get a warning; `personas[]` does not take one at all. See the schema reference for why.) The app itself takes two: `app.description` is the short tile text, and `app.aiDescription` is the **routing description** an orchestrator reads to choose between apps — who the app is for, what it covers and excludes, and how to tell it from a sibling app over the same tables. Write it whenever such a sibling exists or is planned. - **Level (b) — artifacts + page-intents + design**: **enumerate every surface each job needs and classify it** per the genpage-first policy above — record CRUD → form + view; anything else (overview/landing, dashboard, KPIs, analytics, guided/wizard flow, composite or comparison screen) → a page **intent** (`source: { kind: "intent" }`). State each classification out loud; if the app needs no pages, say so and why rather than silently omitting them. Then forms + views + charts + sample data and the optional `design` contract, mapping each job to its surfaces via `jobs[].surfaces[]`. **No page `.tsx` here.** `dashboards[]` only on explicit request. - **Level (c) — access** (`personas[].jobs[].privileges[]`): personas and jobs already exist from (a0) and the entities now exist, so **only add the privileges each job needs** — don't re-ask who the users are. The builder unions them into one role per persona and grants the app to it so it **opens for that persona, not just sysadmins**. **Render the roles + per-entity access as a table in your chat reply** (the user can't approve an access model they can't see — see the CRITICAL note above). If they want no roles, tell them jobs live in `personas[]` so dropping the roles drops the recorded jobs too; offer read-only privileges to keep the record. (Column-level security and access teams are not yet supported — see *Notes & limits*.) - **Whole-app preview** (design gate for Level (b)): `node "${PLUGIN_ROOT}/scripts/preview-app.js" --spec @<working-dir>/app-spec.json` renders data-model + sitemap + form wireframes + page-intents + design contract. **Reproduce the ENTIRE rendered output verbatim in your chat reply, inside a fenced ` ``` ` code block — do NOT leave it in the (collapsed, invisible) tool output, and do NOT just summarize "the preview looks right" (see the CRITICAL note above).** The user must be able to SEE each form, the sitemap, and
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub