| name | update-model-driven-app |
| description | Use when an agent must PROGRAMMATICALLY UPDATE an existing model-driven Power Apps app (including its custom pages) in a real Power Platform environment — typically reusing a service principal (SPN). Owns the "there has to be a way" question: it splits the update surface (record/config data · the model-driven solution shell · custom pages) into the right mechanism per surface, front-loads the SPN-rights + environment-policy gates, and enforces the non-obvious safety facts (canvas .pa.yaml is read-only, unmanaged solution import is irreversible, pack exit-0 ≠ success). Triggers on: "update the app in my power platform environment", "change the model-driven app programmatically", "edit a custom page with code / headless", "push a solution change via pac / SPN", "claude update power apps", "update sitemap / form / view via the API", "import solution service principal".
|
| license | MIT |
| compatibility | pac CLI ≥ 2.4.1, Dataverse Web API v9.2, Power Platform solutions ALM |
| metadata | {"author":"custom","version":"1.0.0","platform":"Microsoft Power Platform","verified":"2026-06-09 against learn.microsoft.com (pac solution/auth, custom-pages ALM, unmanaged-import, canvas .pa.yaml)"} |
Updating a model-driven app (with custom pages) programmatically
You help an agent update an existing model-driven app in a real Power Platform
environment, usually with a service principal that already has Dataverse access (e.g. it
runs cloud flows). The instinct — "headlessly hand-edit the app source and import it" — is a
trap. The platform splits this into three surfaces with three different mechanisms, and
two facts (verified against Microsoft Learn) reshape the whole thing:
- Canvas/custom-page
.pa.yaml is READ-ONLY — "any changes to the file are ignored and
might be lost." You cannot headlessly hand-patch a custom page. The sanctioned AI path is
the canvas authoring MCP server + a live coauthoring Power Apps Studio session (Microsoft
ships this as a preview that names Claude Code / Copilot CLI: /configure-canvas-mcp,
/edit-canvas-app).
- Unmanaged solution import is IRREVERSIBLE — "changes applied by importing an unmanaged
solution cannot be uninstalled. Do not install an unmanaged solution if you want to roll
back." A backup zip is a forensic artifact, not an undo button (property overwrites can
be re-overwritten back; added/deleted structural components cannot be cleanly undone).
Companion knowledge: ../../knowledge/model-driven-app-update-paths.md
(the gotchas + the decision aid), ../../knowledge/dataverse-token-acquisition.md
(get the SPN bearer token), and ../../knowledge/programmatic-flow-creation.md
(the Web-API-update precedent — assumes you already hold a token).
Choose the update path by surface (decision aid — traverse top-to-bottom)
flowchart TD
START["Update a model-driven app"] --> Q1{What am I changing?}
Q1 -->|"Record data, or an env-variable VALUE, or a config row"| DATA["Dataverse Web API record write\n(SPN already authorized; NO solution cycle; reversible via read-before-write snapshot)"]
Q1 -->|"App shell: sitemap, forms, views, env-var DEFINITION"| SHELL["pac solution export -> edit -> pack -> import -> publish\n(rehearse in a sandbox copy FIRST; unmanaged-into-DEV is additive/irreversible)"]
Q1 -->|"A custom page (canvas)"| CANVAS["Canvas authoring MCP + live coauthoring Studio session\n(/configure-canvas-mcp, /edit-canvas-app — semi-interactive, validates live)\nNEVER hand-edit .pa.yaml (read-only/ignored); NEVER use deprecated pac canvas pack"]
Q1 -->|"Promote DEV -> TEST/PROD"| PROMO["Managed solution + Power Platform Pipelines\n(managed uninstall is the real rollback)"]
| Surface | Mechanism | Headless? | Reversible? |
|---|
| Record data / env-var value / config row | Dataverse Web API write | ✅ fully | ✅ read-before-write snapshot → PATCH back |
| Model-driven shell (sitemap / form / view / env-var definition) | pac solution round-trip | ✅ (rehearse first) | ⚠️ additive — sandbox-rehearse + presence/connection checks |
| Custom page (canvas) | canvas authoring MCP + coauthoring Studio | ⚠️ semi-interactive | ✅ MCP validates live + can revert |
| Promotion DEV→higher | managed solution + Pipelines | ✅ | ✅ managed uninstall |
Phase 0 — pre-flight (abort gates; run before any mutation)
pac auth create --name custDEV --applicationId "$SPN_APP_ID" --clientSecret "$SPN_SECRET" --tenant "$TENANT_ID" --environment "$ENV_URL"
pac org who
pac --version
pac solution list
- SPN-rights gate. Running cloud flows does not imply solution-import rights. Read the
SPN's roles and require System Customizer or System Administrator:
GET /api/data/v9.2/systemusers?$filter=applicationid eq '<SPN_APP_ID>'&$expand=systemuserroles_association($select=name).
If absent → ABORT with a precise ask: granting the role is a customer PP-admin action.
(Don't retry the eventual 403 — read the error, name the cause: an insufficient-scope
403 selects "grant the role", not "switch surfaces".)
- Policy gate. Probe
Block unmanaged customizations. If ON, the solution path is
managed-only; record-writes + env-var values are still allowed.
- Environment gate. Refuse to proceed unless the active org URL matches the configured
DEV URL (a fail-closed allow-list). The SPN can reach other environments — don't let a
mis-selected profile write to TEST/PROD.
Phase 1 — the safe slice first: record data + env-var values (fully headless)
Acquire the SPN bearer token (/.default scope — see token-acquisition). Read-before-write
snapshot the target rows, then PATCH. No solution cycle, smallest blast radius, clean rollback.
curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"$ENV_URL/api/data/v9.2/environmentvariablevalues(<value-id>)" -d '{"value":"<new>"}'
Acceptance: GET back the new value; row counts unchanged unless intended; snapshot archived.
Rollback: PATCH back from the snapshot. Ship this capability first — it proves auth +
the verify loop.
Phase 2 — model-driven shell via pac solution (rehearse, then apply)
- Rehearse in a throwaway sandbox copy first. Never let the first run target the
flow-bearing env — a structural mistake there cannot be cleanly undone (unmanaged import is
irreversible).
- Backup (forensic):
pac solution export --managed false + pac solution create-settings
(config snapshot) + a SHA256 manifest.
- Unpack (
pac solution unpack ≥ 2.4.1 → YAML), edit the sitemap/app-module/form/view source.
- Pack-omission guard (exit 0 ≠ success): before packing, assert every path in
solutioncomponents.yml / rootcomponents.yml resolves to a file on disk; after packing,
re-unpack the output zip and diff it against the intended edit. pac solution pack
"still succeeds but omits the component" when a source file is missing — never trust the exit
code alone.
pac solution version bump → pac solution pack → pac solution import (async; poll the
ImportJob) → publish only after the job reports Succeeded.
- Connection-reference guard: snapshot
connectionreference rows pre-import; assert they
still resolve post-import (this is what protects the flows that share the environment).
Acceptance: bumped version live (pac solution list); the change present; connection refs
intact; no app-error banner.
Phase 3 — custom pages via the canvas authoring MCP (semi-interactive, sanctioned)
A custom page is a canvas app embedded in the solution. Do not hand-edit its .pa.yaml
(read-only/ignored) and do not use pac canvas pack/unpack (deprecated). The supported
AI path:
- A human opens the custom page in Power Apps Studio with coauthoring enabled; ensure
the .NET 10 SDK; run
/configure-canvas-mcp with the Designer URL.
- Drive the edit via
/edit-canvas-app — natural-language change → .pa.yaml generated →
validated against the live canvas authoring server → synced to the coauthoring session.
- Code-component pages: if the page uses a PCF/code component, even Git-integration source
editing is unsupported ("editing prevents the app from running") — route to the MCP path only.
- Ordered re-publish: after the custom page publishes, re-publish the model-driven app
or it keeps serving the previous custom page (no error is raised).
Acceptance: the page renders the change in DEV (scripted browser load + snapshot); the app was
re-published.
Non-negotiable safety rules (the facts that bite)
.pa.yaml is read-only — headless hand-edits are ignored/lost. Custom pages go through
the canvas authoring MCP + a live Studio session.
- Unmanaged import is irreversible — a backup is forensic, not undo. Rehearse in a sandbox;
prefer additive/property changes in DEV; use managed + Pipelines for promotion reversibility.
pac solution pack exit 0 ≠ success — silent component omission; assert presence pre-pack
and diff post-pack.
- SPN running flows ≠ solution-import rights — probe
systemuserroles; abort + ask if the
customization role is missing.
- Custom pages need an ordered re-publish of the model-driven app.
- Reads/identity: confirm the org URL is the intended DEV env before every write.
This skill is the operational playbook; the durable priors + the decision aid live in
../../knowledge/model-driven-app-update-paths.md.