genpage
SkillCloud & infraCreates, updates, and deploys Power Apps generative pages for model-driven apps using React v17, TypeScript, and Fluent UI V9. Orchestrates specialist agents for planning, entity creation, and code generation. Use it when user asks to build, retrieve, or update a page in an existing Microsoft Power Apps model-driven app. Use it when user mentions "generative page", "page in a model-driven", or "genux". This skill stands alone and does not require /app-builder — but if the user wants a whole app built (tables, forms, views, sitemap) rather than pages for an app that already exists, use /app-builder instead.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the genpage skill
What this skill tells your AI
The instructions your AI receives, as published by microsoft/power-platform-skills in plugins/model-apps/skills/genpage/SKILL.md and read by ahel’s review.
Plugin check: Run
node "${PLUGIN_ROOT}/scripts/check-version.js"— if it outputs a message, show it to the user before proceeding.
Power Apps Generative Pages Builder
Triggers: genpage, generative page, create genpage, genux page, build genux, power apps page, model page Keywords: power apps, generative pages, genux, model-driven, dataverse, react, fluent ui, pac cli Aliases: /genpage, /gen-page, /genux
Overview
This skill orchestrates specialist agents across the create and edit flows:
Create flow:
genpage-planner— validates prerequisites, gathers requirements, detects what entities and apps exist, presents a plan for approval, writesgenpage-plan.md. May pause and returnconnector_discovery_required(see 2).genpage-connector-builder— top-level orchestrator dispatch for connector feature-gating and discovery; writesconnector-bindings.md+connectors.json. Runs after the planner has resolved create-vs-edit and the target environment (discovery is mutating), then the planner is re-invoked with its contract.genpage-entity-builder— creates Dataverse entities (tables, columns, relationships, choices, sample data) via the plugin's Node.js Web API scriptsgenpage-page-builder— generates one complete.tsxfile per page; multiple builders run in parallel for multi-page requests
Edit flow:
genpage-connector-builder— top-level orchestrator dispatch when an edit adds, replaces, discovers, or clears connector bindings; preserves unchanged bindings when the feature gate is OFFgenpage-edit-planner— reads the downloaded page artifacts, gathers change requirements, presents an edit plan, writesgenpage-edit-plan.md
You (the skill) coordinate the agents and own connector dispatch, app creation, RuntimeTypes generation, deployment, browser verification, and the inline application of planned edits.
References
- Code generation rules: rules.md
- Troubleshooting: troubleshooting.md
- Sample pages: samples/
Development Standards
- React 17 + TypeScript — all generated code
- Fluent UI V9 —
@fluentui/react-componentsexclusively (DatePicker from@fluentui/react-datepicker-compat, TimePicker from@fluentui/react-timepicker-compat) - Single file architecture — all components, utilities, styles in one
.tsxfile - No external libraries — only React, Fluent UI V9, approved Fluent icons, D3.js for charts
- Type-safe DataAPI — use RuntimeTypes when Dataverse entities are involved
- Responsive design — flexbox, relative units, never
100vh/100vw - Accessibility — WCAG AA, ARIA labels, keyboard navigation, semantic HTML
- Complete code — no placeholders, TODOs, or ellipses in final output
Instructions
Follow these phases in order for every /genpage invocation.
Phase 0: Create Working Directory
Derive a short folder name from the user's requirements:
- Extract the page name or a 2-4 word summary from
$ARGUMENTS - Convert to kebab-case (e.g., "Candidate Tracker" →
candidate-tracker) - Create the folder:
mkdir -p <folder-name>(bash/PowerShell; on cmd usemkdir <folder-name>, which has no-pand errors if the folder exists) - Resolve its absolute path — this is the working directory for all subsequent phases
Phase 0.5: Initialize Local-Dev Manifest
Write package.json and genpage.d.ts into the working directory so the
developer can npm install and get IntelliSense, type-checking, and "go to
definition" in their editor. Versions come from
references/supported-dependencies.md (single source of truth:
scripts/lib/supported-dependencies.js).
node "${PLUGIN_ROOT}/scripts/generate-page-manifest.js" <working-dir> <kebab-slug>
<kebab-slug>is the same slug used for the working directory.- Add
--features charts,datepicker,timepicker(comma-separated) only when the requirements clearly call for them; otherwise omit and keep the manifest lean. - The script is idempotent — it skips files that already exist. Pass
--forceto overwrite (used in regeneration flows when versions drift). - Output is a JSON summary on stdout; pipe to stderr for visibility but do not block the workflow if the script returns non-zero — the manifest is a dev-ergonomics aid, not part of the deployed artifact.
Phase 1: Plan
⚠️ CRITICAL — you MUST invoke
genpage-plannervia theTasktool. You MUST NOT inline the planner's questions yourself withAskUserQuestion.The planner is not optional or skippable. It runs:
- Prerequisite validation (
node --version,pac helpversion > 2.10.0)- Auth verification (
pac auth list, environment selection)- The structured "Create new / Edit existing" question (via
AskUserQuestioninside the planner subagent, not here)- Language detection (
pac model list-languages) — only on new-page path- Entity existence detection (
pac model list-tables --search)- App detection (
pac model list) with proper selection prompts- Plan-mode presentation and approval
- Writes
genpage-plan.mdto the working directoryReasons to NEVER ask "new or edit?" yourself before invoking the planner:
- You would skip prereq + auth (the planner is the only thing that runs them)
- The structured question gives the user labeled options; an inline free-text prompt forces them to guess
- The planner returns
{ "action": "edit" }as a contract — your inline question can't produce that signal cleanlyEven if
$ARGUMENTSlooks like it tells you the intent, still invoke the planner. Pass the intent in the prompt — the planner uses it to skip its own Question 1 if appropriate, but the prereq/auth/env steps still run.
Steps
- Invoke
genpage-plannerviaTaskwith the prompt below. Connector discovery has not run yet, so tell the planner the contract is the literalNo connector bindings.and that discovery is available on request (see 1a). - Wait for it to finish (it returns a summary).
- If the return includes
{ "action": "connector_discovery_required" }, invokegenpage-connector-builderwith the returned intent — Mode:createfor a new page, Mode:editif the planner also reported an edit — using the environment URL the planner resolved, then invokegenpage-planneragain with the builder's## Connector Bindingscontract andconnectors.jsonstatus. - If the return includes
{ "action": "edit" }, jump to the Edit Flow section. - Otherwise the planner has written
genpage-plan.md. Proceed to Phase 2.
1a. Connector discovery is orchestrator-owned and never speculative
genpage-connector-builder is dispatched only by this top-level orchestrator,
not by genpage-planner. This keeps the connectors feature gate in one agent
while avoiding nested Task calls from the planner.
Never run discovery before the planner returns — not even when $ARGUMENTS
obviously mentions SharePoint, Teams, Office 365 or a custom REST source.
Discovery is a mutating operation: it can create a connection reference. The
planner is what resolves (a) create vs. edit and (b) which environment, and it may
resolve either differently from the active pac auth profile. A connection
reference created in the wrong environment, or in create mode for what turns out
to be an edit, cannot be undone by discarding the local outputs.
So the sequence is always: plan first, then discover, then re-plan.
- Every first planner invocation gets the literal contract
No connector bindings.and is told discovery has not run. - When the planner determines connector-backed data is needed — from
$ARGUMENTSor from its own user clarification — it returns{ "action": "connector_discovery_required", "intent": "..." }together with the resolved action and environment URL. - Only then dispatch
genpage-connector-builderwith that mode, that environment URL, the working directory and${PLUGIN_ROOT}. Read<working-dir>/connector-bindings.mdand verify<working-dir>/connectors.jsonis a bare JSON array, then re-run the planner with the refreshed contract.
The builder remains the single owner of the connectors flag: it probes first,
writes No connector bindings. + [] when the flag is OFF, and performs all
connection discovery only when the flag is ON.
Invocation prompt
Pass a prompt that includes:
- The user's requirements:
$ARGUMENTS - The working directory (absolute path from Phase 0)
- The plugin root path:
${PLUGIN_ROOT} - The connector contract: the full body of
<working-dir>/connector-bindings.md, or the literalNo connector bindings.when discovery was not needed or the feature gate was OFF - The connector upload file status:
<working-dir>/connectors.jsonexists and is a bare JSON array, orno connectors.json; omit --connectors
Example:
You are the genpage-planner agent. Plan generative page(s) for the following requirements:
[paste $ARGUMENTS here verbatim, or "no arguments provided — gather from user"]
Working directory: [absolute path from Phase 0] Plugin root: ${PLUGIN_ROOT}
Connector discovery is orchestrator-owned. Do not invoke
genpage-connector-builderfrom inside the planner. Use this as the entire## Connector Bindingssection of the plan:[paste connector-bindings.md body, or
No connector bindings.]Connector upload file: [absolute path to connectors.json, or
none — omit --connectors]If your clarification questions reveal connector-backed data that is not covered by the connector contract above, stop and return
{ "action": "connector_discovery_required", "intent": "<connector need>", "resolvedAction": "create" | "edit", "envUrl": "<the environment you resolved>" }instead of trying to discover connectors yourself.resolvedActionandenvUrlare required — discovery is dispatched against exactly those.Follow the instructions in your agent file. Validate prereqs, confirm auth, ask the new/edit question via AskUserQuestion, then proceed accordingly. Write genpage-plan.md to the working directory if creating. Return the page list, entity status, app selection, and any
{ "action": "edit" }signal when complete.
Phase 2: Create Entities (Conditional)
Read genpage-plan.md from the working directory. Check the Entity Creation Required
section.
If the section literally says "No entity creation required — all entities already exist": Skip to Phase 3.
If entities need creating:
2a. Pre-flight: az + pac + Dataverse
Entity creation runs through the plugin's Node.js Web API scripts using az for
auth, and the az and pac identities should normally match. Run the
consolidated pre-flight:
node "${PLUGIN_ROOT}/scripts/check-auth.js" --require-pac
Genpage deploys pages via pac model genpage, so pass --require-pac to keep a missing pac login
a hard blocker (the app-builder skill omits the flag — its build path only needs the az token).
It returns a single JSON object:
{
"ok": true | false,
"blocker": null | "az_missing" | "az_not_logged_in" | "pac_not_logged_in"
| "no_env_url" | "whoami_403" | "whoami_401" | "whoami_error",
"message": "human-readable next step",
"warnings": ["..."],
"azUser": "...", "pacUser": "...", "envUrl": "...",
"identitiesMatch": true | false,
"whoAmI": { "ok": true, "userId": "...", "organizationId": "..." }
}
ok: trueandidentitiesMatch: true→ proceed to 2b.ok: trueandidentitiesMatch: false→ proceed to 2b but surface themessageto the user as an inline warning ("az is X, pac is Y — WhoAmI works for now, but if entity creation later returns 403, run the suggestedaz login --usernameto align them").ok: false→ show themessagefield to the user verbatim and stop the workflow. The script already includes a fix-it command for every blocker (runaz login, etc.).
Capture envUrl from the result — Phase 2b passes it to the entity-builder.
2b. Invoke entity-builder
Invoke the genpage-entity-builder agent via the Task tool. Pass in the prompt:
- Path to
genpage-plan.md - Working directory (absolute path)
- Plugin root:
${PLUGIN_ROOT} - Dataverse env URL (from
pac org who)
The entity-builder reads Solution and Publisher Prefix directly from the
plan's ## Environment — no need to re-thread them here.
Wait for completion. The builder writes a transactional log at
<working-dir>/genpage-entity-creation-log.md for recovery on failure.
Phase 3: App Creation/Selection
Read genpage-plan.md for the app decision and the Solution line in
## Environment.
If "create new":
pac model create --name "App Name" --solution "<Solution unique name>" --publish
--solution is mandatory. pac model create errors out with
"The given solution name is not valid: ()" if you omit it — its claimed
"active solution" fallback does not work in practice.
--publish is mandatory. Without it the new appmodule stays in draft and
the genux runtime URL errors with "app not published".
- Use the plan's
Solutionvalue verbatim. The planner always writes one (default fallback is literallyDefault). - If the plan is somehow missing
Solution, pass--solution Default— every Dataverse env has a built-in "Default Solution" by that unique name.
Store the new app-id for Phase 6.
If existing app-id: Use it directly. pac model create is not called, so
the Solution line is informational only for this phase.
Phase 4: Generate RuntimeTypes (Conditional)
If any page uses Dataverse entities, generate the TypeScript schema:
pac model genpage generate-types --data-sources "entity1,entity2,..." --output-file <working-dir>/RuntimeTypes.ts
Windows + Bash: Always use forward slashes in file paths (e.g.,
D:/temp/RuntimeTypes.ts).
After generating, read the RuntimeTypes.ts file to verify it generated correctly.
For mock data pages only: Skip this phase.
Phase 4.5: Connector Bindings (Conditional)
Re-probe the feature gate here — do not rely on the plan content alone. A plan authored while the flag was ON must not deploy connectors after it is turned OFF:
node "${PLUGIN_ROOT}/scripts/lib/feature-flags.js" connectors
If it prints disabled: connectors are OFF. Skip this phase entirely — do not
create or pass connectors.json, and never add --connectors on upload —
regardless of what the plan's ## Connector Bindings section says. (Backstop:
list-connections.js / create-connection-reference.js also fail closed with
exit 3 if invoked while OFF.)
Carry this decision into code generation. The probe result is Connectors: disabled / Connectors: enabled for the rest of the run, and Phase 5 must pass
it verbatim in every page-builder dispatch. When it is disabled, every downstream
step treats the plan's ## Connector Bindings section as if it read
No connector bindings. — otherwise the generated page would call a connector that
this run deliberately never binds, and the page fails at runtime instead of simply
omitting the feature.
If it prints enabled: read the plan's ## Connector Bindings section and
treat it as bindings only when it contains an actual binding table (a
| Logical Name | … header with at least one data row). If the section is
No connector bindings., empty, missing, or malformed, treat the page as having
no connectors and skip this phase.
When there are real bindings, the genpage-connector-builder agent already wrote
<working-dir>/connectors.json during planning — verify it exists and matches the
plan table. If it is missing, derive it from the plan table as a bare JSON
array (never the { "connectorBindings": [...] } object wrapper — that is the
deployed page config.json shape that pac writes):
[
{
"logicalName": "new_uxtest_sharepoint",
"connectorId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
"dataset": "https://host.sharepoint.com/sites/x",
"tables": ["5709dd6f-c73e-4079-ad23-2334e45e0e13"],
"tableDisplayNames": ["Pet"]
},
{
"logicalName": "new_uxtest_msnweather",
"connectorId": "/providers/Microsoft.PowerApps/apis/shared_msnweather",
"dataset": "",
"operations": ["CurrentWeather"]
}
]
Do not write connection IDs into connectors.json — the importing maker/admin
fills env-specific ConnectionId values through solution deployment settings.
Phase 4.6: Custom API Bindings (Conditional)
Re-probe the feature gate here — do not rely on the plan content alone. A plan authored while the flag was ON must not deploy Custom API bindings after it is turned OFF:
node "${PLUGIN_ROOT}/scripts/lib/feature-flags.js" custom-api
If it prints disabled: Custom API support is OFF. Skip this phase entirely — do not
create or pass actions.json, and never add --actions on upload — regardless of what the
plan's ## Custom API Bindings section says. (Backstop: list-custom-apis.js also fails
closed with exit 3 if invoked while OFF.)
If it prints enabled: read the plan's ## Custom API Bindings section and treat it as
bindings only when it contains an actual binding table (a | Name | Kind | … header with
at least one data row). If the section is No custom API bindings., empty, missing, or
malformed, treat the page as having no Custom APIs and skip this phase.
When there are real bindings, the genpage-customapi-builder agent already wrote
<working-dir>/actions.json during planning — verify it exists and matches the plan table. If
it is missing, derive it as a bare JSON array of
{ name, isFunction, boundEntityLogicalName?, displayName, parameterKinds } entries (Action row
isFunction:false, Function true; boundEntityLogicalName only for an entity-bound, non-
(Global), row) — see ${PLUGIN_ROOT}/references/custom-api.md. Never the
{ "actionBindings": [...] } object wrapper (the deployed config.json shape pac writes).
Phase 4.7: Page Telemetry (Conditional)
Re-probe the feature gate here — do not rely on the plan content alone.
node "${PLUGIN_ROOT}/scripts/lib/feature-flags.js" custom-telemetry
This phase has no bindings and no artifacts; it only decides whether page-builder is
permitted to instrument. If it prints disabled, generated pages contain no
telemetry calls at all — identical to before the feature existed.
Carry this decision into code generation. The probe result is Telemetry: disabled / Telemetry: enabled for the rest of the run, and Phase 5 must pass it
verbatim in every page-builder dispatch.
enabled is permission, not instruction. Even when it is on, page-builder emits
telemetry only when the maker asked to measure or track something in their own
words; the default output is still a page with zero telemetry. See
${PLUGIN_ROOT}/references/page-telemetry.md.
Phase 5: Build Pages (Parallel)
Read genpage-plan.md and extract the pages table.
5a. Validate the plan before dispatch
Before invoking any builders, verify:
- At least one page exists in the
## Pagestable - Every page has a
### [Page Name]subsection in## Per-Page Specifications - All filenames in the
## Pagestable are unique. If any are duplicated, rewrite the plan appending-1,-2, etc. before dispatch. Duplicate filenames cause silent last-writer-wins data loss under parallel execution.
See ${PLUGIN_ROOT}/references/plan-schema.md for the full contract.
5b. Single-page fast path (skip Task dispatch when N=1)
If the plan's Pages table contains exactly one row, do NOT dispatch a Task subagent. Inline the page-builder workflow directly in the orchestrator:
- Read
${PLUGIN_ROOT}/references/rules.md - Read the sample listed in the plan's
## Relevant Samples - Only when the Phase 4.5 probe printed
enabledand the plan's## Connector Bindingssection contains an actual binding table (a| Logical Name | …header with at least one data row), also read${PLUGIN_ROOT}/references/connectors.md. Treat aNo connector bindings.sentinel, an empty/missing/malformed section, or adisabledprobe as having no connectors (same contract as Phase 4.5 and genpage-page-builder). 3b. Only when the plan's## Custom API Bindingssection contains an actual binding table (a| Name | Kind | …header with at least one data row), also read${PLUGIN_ROOT}/references/custom-api.md. Treat aNo custom API bindings.sentinel, or an empty/missing/malformed section, as having no Custom APIs (same contract as Phase 4.6 and genpage-page-builder). - If the plan's Per-Page Specification has
Needs caching: true, also read${PLUGIN_ROOT}/references/data-caching.md - If the plan's
## Environmentindicates non-English languages, also read${PLUGIN_ROOT}/references/localization.md5b. Only when the Phase 4.7 probe printedenabledand the maker's own request asks to measure, track, monitor, or diagnose something, also read${PLUGIN_ROOT}/references/page-telemetry.md. In every other case the page contains no telemetry calls — do not read it. - Read
genpage-plan.md(already in working directory) andRuntimeTypes.tsif Data mode is dataverse - Write the
.tsxfile to<working-dir>/<filename>.tsxfollowing all rules - After writing, Grep every named import from
@fluentui/react-iconsagainst${PLUGIN_ROOT}/references/verified-icons.txt(one Grep per name). Rewrite any unverified names with the closest verified alternative; do not load the full icon list into context - Proceed to Phase 6
This saves ~5-15s of Task overhead and ~3K tokens that would otherwise be duplicated in a subagent context.
5c. Multi-page: invoke page-builders in parallel
If the plan's Pages table contains 2+ rows, invoke a genpage-page-builder
agent via the Task tool per page. Fire all invocations in a single message
for parallel execution.
For each page, pass a prompt that includes:
- Page name (e.g., "Candidate Tracker")
- Target file name (e.g., "candidate-tracker.tsx")
- Absolute path to
genpage-plan.md - Data mode (see below) — either a RuntimeTypes path or an explicit mock flag
- Connectors:
enabledordisabled— the Phase 4.5 probe result, verbatim - Telemetry:
enabledordisabled— the Phase 4.7 probe result, verbatim - Working directory
- Plugin root:
${PLUGIN_ROOT}
For Dataverse pages, include the RuntimeTypes line:
You are the genpage-page-builder agent. Generate the [Page Name] page.
- Target file: [filename].tsx
- Plan document: [absolute path to genpage-plan.md]
- Data mode: dataverse
- Connectors: [enabled|disabled from Phase 4.5]
- Telemetry: [enabled|disabled from Phase 4.7]
- RuntimeTypes: [absolute path to RuntimeTypes.ts]
- Working directory: [absolute path from Phase 0]
- Plugin root: ${PLUGIN_ROOT}
Follow the instructions in your agent file. Write [filename].tsx and return your result when done.
For mock data pages, omit the RuntimeTypes line and set Data mode: mock:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 859
- Forks
- 176
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
genpage- Source
- github.com/microsoft/power-platform-skills