Over the last two months I have brought three large Power Platform solutions under source control and moved them through sandbox and production with the pac command-line tool rather than the portal. Between them they contain four canvas apps and more than a hundred flows. The pattern that made them manageable is the parent and child flow, and the thing that made the moves painful is a list of ALM traps that are documented nowhere in one place. Here is both.
The parent and child pattern
A canvas app calls a parent flow with the Power Apps trigger. The parent does nothing except POST the inputs to a child flow’s “When an HTTP request is received” trigger and return the child’s response. The child does the real work, such as a SQL query or a SharePoint update, and ends with a Response action that has a typed JSON schema.
Why split them? The parent runs under the connections of whoever is using the app. The child runs under the connections of its owner. So:
- Never put a connector in the parent. If you do, every user needs their own connection to it and the app breaks for anyone who has not consented.
- The child is reusable from other flows and from other apps.
- The typed Response schema is what Power Apps uses to build the return type, so the app gets proper fields instead of a text blob.
The ALM traps
Each of these cost real time. In rough order of how much.
Imports reset run-only sharing. After a solution import, every parent flow that users had been granted run-only access to lost that sharing. Every non-admin user then hung on the app’s loading screen with a Dataverse error (0x80048306). I found it by reading the app’s own error log through Graph. After every import, check flow activation and run-only sharing.
Imports switch scheduled flows off. Related and equally silent. I now run a post-import script that lists every flow in the solution and turns on the ones that should be on.
Exports write secrets into defaultValue. When you export a solution, the current values of environment variables are written into the variable definitions. That includes client secrets and child flow URLs with their sig= signature. If you commit the export you have committed your secrets. Strip defaultValue before committing, or use a settings file at import time.
A custom connector that is not solution-aware blocks export. Legacy connectors created outside a solution cannot be exported with it. The fix is to create a solution-aware copy and repoint every action in every flow. I wrote a script that rewrites only three things in the flow JSON: the connector’s host.apiId, the connectionName and the connection reference key. Fifteen actions, one script, no portal clicks.
Flow JSON is CRLF with no trailing newline. Save it with LF and a one-line change becomes an 800-line diff. Set your editor and git attributes to leave those files alone.
Removed screens survive in _EditorState.pa.yaml. Deleting a screen from the unpacked canvas app source without removing it from the editor state file gives ErrOpeningDocument on import.
pac canvas unpack returns exit 1 on group containers. It is harmless, so do not fail your pipeline on the exit code. Compare the unpacked control count instead.
Environment variables are cached. A flow keeps using the old value after you edit the variable directly, and a hand-built flow gets null unless you re-pick the variable in the designer. In my tests the cache refreshed on its own within a couple of minutes, but do not assume.
SharePoint filters have a length. An OData filter with around 600 or clauses returns a 400. Batch it.
Rotating an app secret without Key Vault
One more pattern that fell out of this work. The child flows authenticate to Graph with a client secret, and secrets expire. A scheduled flow calls Graph addPassword on the app registration, writes the new secret to the environment variable, and only removes the old secret once its own expiry has passed. The overlap means nothing breaks while the cache above catches up. It is not Key Vault, but it has rotated cleanly for months.
The habit that matters
Keep a one-page checklist for after every import: activation, run-only sharing, connection references, environment variable values, and a smoke test from a non-admin account. Every trap above is invisible from an admin account.