7.1The Dataverse trigger
Change type, table, scope, and the two filters
A flow triggered from Dataverse runs after the operation that started it has committed (§6.5). What you still get to decide is how often it starts, and you decide that in the trigger's own settings rather than in the flow's first condition.
The When a row is added, modified or deleted trigger runs a flow whenever a row of a selected table and scope changes or is created, and it takes three required parameters: the trigger condition Change type, the Table name, and the Scope. Change type sets which combination of changes runs the flow, and triggerOutputs()['body/SdkMessage'] carries Create, Update or Delete accordingly. The trigger does not support triggering on relationships of type 1:N or N:N.citedsource: Power Automate docs — Trigger flows when a row is added, modified, or deleted — the required parameters
Scope names the rows that are watched, by ownership: User for rows owned by the flow's user, Business Unit for rows owned by anyone in their business unit, Parent: Child business unit for that business unit or a child of it, and Organization for actions taken by anyone within the environment.citedsource: Power Automate docs — Trigger flows when a row is added, modified, or deleted — the four scopes
Scope and the two filters below do different work, and confusing them is how you get a flow that runs constantly and exits on its first condition. Scope decides whose rows; the column filter decides which changes; the filter expression decides which resulting rows.
7.1.1Filtering columns, and the filter expression
The Select columns box takes a comma-separated list of unique column names and defines which columns, when included in the request, run the flow. It applies to the Update condition alone — Create and Delete apply to all columns of a row — and it is not supported on virtual tables. The flow triggers when a listed column is included as part of the update whether or not the data in it changed, so a column that is always present on update, such as the row's primary key, can cause all updates to trigger the flow. Lookup columns are not supported in this filter: a change to one does not trigger the flow.citedsource: Power Automate docs — Trigger flows when a row is added, modified, or deleted — Select columns
The filter expression is an OData-style expression — firstname eq 'John', contains(firstname,'John') — and the flow runs only when the expression evaluates to true after the change is saved in Dataverse. The expression does not carry the string $filter=, which applies when the APIs are called directly.citedsource: Power Automate docs — Trigger flows when a row is added, modified, or deleted — the filter expression
Two advanced parameters change what a run is rather than whether it happens. Delay until takes an OData-style timestamp and delays the trigger to a UTC time, and, unlike the standard Delay until action, the Dataverse property never expires, so a run can wait for long periods. Run as sets the user context for the Dataverse actions in the flow — Flow owner, Row owner, or Modifying user — and requires the flow owner to hold the prvActOnBehalfOfAnotherUser privilege, which the Delegate security role carries by default.citedsource: Power Automate docs — Trigger flows when a row is added, modified, or deleted — Delay until and Run as
The commonest cause of a run history you did not expect is a filter left empty on a table that something other than a user writes to. An integration or a rollup touching the row nightly triggers your flow on rows nobody edited, and because each run behaves correctly the history looks healthy until you look at the consumption.experientialbasis: flows whose run history was almost entirely runs that ended at the first condition, on tables an integration wrote to on a schedule
Guidance
Set the column filter before you write the flow, not after you look at its run history. A run that starts and exits has already spent a Power Platform request and a run of your daily allowance (§7.5), and the column filter is the one setting that stops it starting.
7.1.2The trigger settings in the stored definition
Those settings are not designer state. A solution-aware flow's definition is JSON on the workflow row, and the trigger's parameters are named in it — so you can review them in a pull request and compare them between environments.
The flow below was authored as a file, packed into a solution and imported, and the capture is the definition read back from Dataverse afterwards.
The connector reference gives the trigger's parameter keys and their types. Change type is message, an integer; Table name is entityname; Scope is scope, an integer; Select columns is filteringattributes; Filter rows is filterexpression; Delay until is postponeuntil; and Run as is runas. It gives the labels and the keys, and not the integer each label corresponds to — which is why the listing above shows the numbers the lab wrote and this chapter does not translate them.citedsource: Power Platform connectors — Microsoft Dataverse — the SubscribeWebhookTrigger parameters
Evidence — the trigger as authored, and what the environment returned
"triggers": {
"When_an_account_row_is_modified": {
"type": "OpenApiConnectionWebhook",
"inputs": {
"host": {
"connectionName": "shared_commondataserviceforapps",
"operationId": "SubscribeWebhookTrigger",
"apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
},
"parameters": {
"subscriptionRequest/message": 3,
"subscriptionRequest/entityname": "account",
"subscriptionRequest/scope": 4,
"subscriptionRequest/filteringattributes": "name,telephone1"
},
"authentication": "@parameters('$authentication')"
},
"conditions": [
{ "expression": "@not(equals(triggerOutputs()?['body/statecode'], 1))" }
]
}
}
conditions sits beside those parameters rather than among them: it holds the trigger condition, which is a separate mechanism from the OData filter expression and is evaluated on the trigger's own output.After the solution import, the environment returned the flow's clientdata carrying the same four subscriptionRequest parameters and the same conditions array, value for value with what the source file declared. The trigger's filter settings survive the packaging and the import as data rather than being re-derived by the designer.verifiedartifact: ch07-trigger-definition · artifacts-ch07.md
Guidance
When you are asking why a flow runs so often, review its trigger in the exported JSON rather than in the designer. The four parameters above are one screen of text and they diff between branches; the designer spreads the same information across two collapsed panes.
7.2Error handling as a discipline
The run-after setting, the scope as try and catch, and terminate
By default a cloud flow stops on a failed action and marks the run failed. You can choose something else, action by action, with the constructs below.
Run after settings specify what happens if an action fails, times out, is skipped, or is successful, and the conditions are set per action, which is how an alternative path is built. The documented example makes a flow send an email notification when a preceding Update a row action fails.citedsource: Power Automate docs — Employ robust error handling — Run after settings
A scope is a container that groups related actions, and when a scope runs the actions inside it execute as one block. Each scope reports a status — Succeeded, Failed, Skipped and others — and scopes work with run-after conditions to build try, catch and finally patterns. When a scope fails it reports a single Failed status for the whole scope, so identifying the action that failed means expanding it. Power Automate supports a maximum of eight nested levels of actions, counting scopes, conditions, switch cases and apply-to-each loops, and a flow deeper than that neither saves nor runs; triggers and response actions are to remain outside scopes.citedsource: Power Automate docs — Use scopes to organize actions in cloud flows — what a scope is and what it reports
The documented try/catch pattern puts the main actions in a Try scope and the error handling in a Catch scope configured to run if the Try scope fails, with the result() function filtered to get the error. The Terminate action stops the flow and sets a status, so that a critical error can end the run as Failed with a status and a message that say why.citedsource: Power Automate docs — Employ robust error handling — the try and catch pattern and Terminate
Three properties of that arrangement decide how it behaves in production. The catch scope runs on the try scope's aggregate status, so a failure anywhere inside the try reaches it. The failing action's identity is inside the scope rather than on it, so your catch has to read result() to find out what went wrong. And a run that ends through Terminate ends with the status you gave Terminate, which is what puts a handled failure into the run history as a failure rather than a success.
Caution
A catch scope that logs and then lets the run end normally gives you a run history of successes. Your failures are in a table somewhere, the flow's own analytics say it is healthy, and the first person to notice is whoever reads that table.
Guidance
End a caught failure with Terminate and a status of Failed, and put the diagnosis in Terminate's message. That keeps the run history answering whether the flow is working, instead of leaving you two places that can disagree.
7.3Retry policies
What an action does before it is allowed to fail
Retries happen before run-after does. An action a policy retries is not a failed action until the policy is exhausted, which is why you configure the two in different places and read them in this order.
The default retry policy depends on the flow's performance profile. On the Low profile it sends up to two retries at exponentially increasing intervals, scaling by 5 minutes up to an interval of approximately 10 minutes for the last retry. On the Medium and High profiles it sends up to 12 retries at exponentially increasing intervals, scaling by seven seconds up to an interval of approximately 1 hour for the last retry.citedsource: Power Automate docs — Limits of automated, scheduled, and instant flows — the default retry policy
Where the default is replaced, the settings are bounded at 90 retry attempts, a retry maximum delay of one day, and a retry minimum delay of five seconds.citedsource: Power Automate docs — Limits of automated, scheduled, and instant flows — the retry setting bounds
A retry policy is aimed at transient failures caused by temporary or intermittent problems with a network or service, and retries at either fixed or exponential intervals. Exponential policies are preferred because they extend the retry period over time and increase the chances of the action completing, avoiding overwhelming the system with frequent retries.citedsource: Power Automate docs — Employ robust error handling — why exponential is preferred
A retry policy on an action that fails deterministically multiplies what the failure costs you and changes nothing about the outcome. A malformed payload is still malformed on the last attempt, and what the run consumes is the attempts rather than the work.experientialbasis: flows retrying a call that returned a deterministic validation error, where the policy turned one rejected payload into a run's worth of identical rejections
Caution
Retries and requests from pagination count as action runs against the Power Platform request limits, alongside every connector, HTTP and built-in action, and both successful and failed actions count.citedsource: Power Automate docs — Limits of automated, scheduled, and instant flows — retries count as requests
7.4Concurrency and ordering
How many runs at once, and what that costs in sequence
A Dataverse-triggered flow is invoked per event, and the platform will not sequence the invocations for a given row unless you tell it to.
When multiple updates occur to a single row, Power Automate evaluates the trigger for each update, even where the updated values are the same as the previous ones, and those updates can result in multiple flow runs.citedsource: Power Automate docs — Trigger flows when a row is added, modified, or deleted — multiple updates to one row
Concurrent runs are unlimited while Concurrency Control is off, and are bounded between 1 and 100 with a default of 25 when it is on. Concurrency Control is a trigger setting, off by default, and turning it on cannot be undone without deleting and re-adding the trigger. With it on, the number of runs that can be queued at the maximum is 10 plus the degree of parallelism, and triggers arriving beyond that might be retried by the connector, with those retries perhaps not succeeding while the waiting limit continues to be met — so the documentation's own advice for making every trigger produce a run is to leave the setting off.citedsource: Power Automate docs — Limits of automated, scheduled, and instant flows — trigger concurrency control
Inside a run, an apply-to-each loop defaults to a concurrency of 1 and takes a value between 1 and 50, and processes at most 5,000 array items on the Low performance profile or 100,000 on the others. Split-on debatching takes the same two ceilings without trigger concurrency, and drops to 100 items with it.citedsource: Power Automate docs — Limits of automated, scheduled, and instant flows — loop and debatching concurrency
So turning concurrency control on buys you bounded parallelism and charges twice: a queue that can drop triggers, and a trigger you have to delete and re-add to undo the setting.
Ordering is something teams assume and rarely write down. Two runs triggered by two edits a second apart can finish in either order, and where both write the same column the value that survives is the one written by whichever run finished last — not necessarily the one that read the later data.experientialbasis: flows that wrote a derived value on every edit of a row, where two runs for two quick edits committed in the order they finished rather than the order they started
Guidance
Where a flow's job is to derive a value from a row, make its write idempotent instead of reaching for concurrency control. A run that recomputes the value from the row it reads at the moment it writes converges whatever order the runs finish in, and you keep the trigger's unbounded parallelism.
7.5Service protection limits and the plan's own
Two ceilings, measured differently, reached by different things
Two separate systems bound your throughput, and the platform evaluates them separately. Dataverse's service protection limits are about how hard one user is pressing the API; Power Automate's request limits are about what a licence entitles a flow to run.
7.5.1Dataverse service protection limits
Dataverse enforces service protection limits per user, per web server, on three facets. The number of requests a user sends is bounded at 6,000 within a five-minute sliding window. The combined execution time of those requests is bounded at 20 minutes — 1,200 seconds — within the same window. The number of concurrent requests is bounded at 52 or higher. Two of the three use a five-minute, 300-second sliding window, and each authenticated user has an independent limit.citedsource: Power Apps docs — Service protection API limits — the three facets and their figures
Exceeding a limit returns 429 Too Many Requests from the Web API and one of three error codes from the SDK for .NET: -2147015902 (0x80072322), "Number of requests exceeded the limit of 6000 over time window of 300 seconds"; -2147015903 (0x80072321) for the combined execution time; and -2147015898 (0x80072326), "Number of concurrent requests exceeded the limit of 52." A Retry-After duration comes back with the error, and the more demanding the preceding requests were, the longer it is.citedsource: Power Apps docs — Service protection API limits — the errors and Retry-After
Service protection limits do not apply to data operations originating from plug-ins and custom workflow activities, which run within the isolated sandbox service and do not use the public API endpoints. The computation time those operations contribute is added to the initial request that triggered them, and that time counts.citedsource: Power Apps docs — Service protection API limits — plug-ins and the sandbox
That last exemption is why the two chapters answer differently on throughput. Work you move into a synchronous plug-in (§6.1) leaves the request count and joins the execution-time budget of the request that triggered it; work you move into a flow enters the request count as its own traffic.
7.5.2The performance profile, and this guide's lab
A flow's performance profile is set by the plan of its owner and decides its Power Platform request limits. The Low profile covers the Free plans, the Microsoft 365 plans, Power Apps Plan 1 and Per App, Power Automate Plan 1, all licence trials, Dynamics 365 Team Member and Microsoft Power Apps for Developer. Power Platform requests are bounded at 100,000 per 5 minutes for every profile, and per 24 hours at 10,000 for Low, 200,000 for Medium, 500,000 for High and 10,000,000 for Unlimited Extended. A single flow run times out after 30 days, and a flow definition is bounded at 500 actions.citedsource: Power Automate docs — Limits of automated, scheduled, and instant flows — performance profiles and request limits
The environment's type and a user's plan are two different records, and the second is what sets the profile above. The first settles which capabilities the environment offers, which is where §3.5 takes it up.
Evidence — the lab environment's type, and the runs this chapter spent
The lab this guide is written in reports its environment type as Developer, which pac admin list shows in its own column beside the environment id.verifiedartifact: ch07-environment-type · artifacts-ch07.md
Listing all environments from your tenant... Listing environment groups from your tenant... Active Environment Group Environment ID Environment Url Type Organization ID * skydude's Environment - 3caaae88-df92-e1ea-9467-7ce90a51b602 https://org85700aeb.crm.dynamics.com/ Developer c5fa366c-5190-f111-b8cf-6045bd056918The other two environments this guide uses are in the same listing and are elided here.
This chapter's experiments consumed no flow runs. Both flows it authored arrived deactivated and stayed so (§7.7), and a count over the flowrun table in the lab environment returned 0.verifiedartifact: ch07-run-consumption · artifacts-ch07.md
7.6Child flows and their contract
What a callable flow owes its caller, and what the platform asks of both
A child flow is the platform's answer to a flow that has outgrown one screen, and to logic you want in more than one place. The arrangement carries requirements that are easy to meet while you are authoring and awkward to retrofit.
Both flows live in a solution, and the documentation's guidance is to create the parent and all child flows directly in the same one, because importing a flow into a solution can give unexpected results. The child flow uses the Manually trigger a flow trigger with inputs defined on it, and returns data through Respond to a Power App or flow or the HTTP Response action. The parent calls it with the built-in Run a Child Flow action, and only flows the caller can access and that sit in a solution are offered.citedsource: Power Automate docs — Create child flows — the requirements on both flows
A child flow using anything beyond built-in actions and the Microsoft Dataverse connector has its connections set to Use this connection rather than Provided by run-only user, because connections cannot be passed from parent to child and child flows support embedded connections alone; without that the platform refuses the flow as a child workflow. While the child runs the parent waits — for one year where the child uses built-in connections and Dataverse, and 30 days otherwise. On export and import into another environment the parent and child are relinked automatically, with no URL to update.citedsource: Power Automate docs — Create child flows — connections and the waiting parent
Child flows are Microsoft's own answer to the 500-action ceiling and to the eight-level nesting ceiling, named in the notes on both.citedsource: Power Automate docs — Limits of automated, scheduled, and instant flows — child flows as a decomposition
The part of that contract that decays is the input list. A child flow's inputs are a signature with no type checking beyond the primitive kinds and no compiler to tell you when a caller stops matching it, and the failure surfaces inside the child as a null where a value was expected.experientialbasis: decompositions where a child flow's inputs were widened over several releases until each caller passed a payload shaped for itself
Caution
No claim in this section was run in the lab. A child flow needs the Manually trigger a flow trigger and a run-only connection setting, and the second of those is a designer surface the CLI session behind this guide does not reach.
7.7Solution-aware flows at deploy time
What an import does to a flow, and what it leaves for a person
A solution-aware flow is a workflow row of category Modern Flow, carried between environments like any other component (§3.6). What the import does to it is where your deployment stops being automatic.
7.7.1A flow authored as a file
The runs in this section used a flow written as solution source rather than in the designer, so what the import did could be compared with what the source declared. A modern flow in solution source is a pair of files under Workflows/: the definition as JSON, and a metadata sidecar naming the component.
Evidence — the sidecar, and the two shapes the packer read differently
<Workflow WorkflowId="{c6701a01-6701-6701-9c67-c67c67c67c67}" Name="ch67DailyNote">
<JsonFileName>/Workflows/ch67DailyNote-C6701A01-6701-6701-9C67-C67C67C67C67.json</JsonFileName>
<Type>1</Type>
<Category>5</Category>
<Mode>0</Mode>
<Scope>4</Scope>
<StateCode>1</StateCode>
<StatusCode>2</StatusCode>
<IntroducedVersion>1.0</IntroducedVersion>
<PrimaryEntity>none</PrimaryEntity>
</Workflow>
The sidecar carries further elements — Subprocess, OnDemand, TriggerOnCreate,
TriggerOnDelete, AsyncAutoDelete, SyncWorkflowLogOnFailure, RunAs, IsTransacted,
IsCustomizable, BusinessProcessType, IsCustomProcessingStepAllowedForOtherPublishers
and LocalizedNames — all of them elided here and present in artifacts-ch07.md.
Category 5 as Modern Flow, and it read the StateCode and StatusCode pair as a starting position rather than an instruction.The packer treats a modern flow as a sharded component: with the workflow's metadata written as children of <Workflows> in Other/Customizations.xml it reported Component: Workflows is a supported component type but has unexpected children in Customizations.xml; this component's specific processing will be skipped and then named the flow's id among the root components it could find no definition for, and with <Workflows /> left childless and the same metadata moved into a .json.data.xml sidecar beside the definition it reported Processing Component: Workflows and named the flow.verifiedartifact: ch07-flow-source-shape · artifacts-ch07.md
7.7.2Off, and owned by whoever imported it
When a solution is imported, the components in it — cloud flows, connection references, apps and the rest — are owned by the user who performs the import. The import then attempts to restore the flows to the state they were in when exported: where the flows were on when exported and the connection references get connections, the flows should be turned on as part of the import. A flow that already exists in the target keeps its current state across an update, and during any import the flows in the solution are turned off and turned on again.citedsource: Power Automate docs — Import a solution — ownership and flow state after import
The readings and the documented behaviour agree, and together they give you a usable rule. The import restores the state a flow was exported in, and a flow authored as a file has not been exported from anywhere; the state declared in its sidecar is a starting position rather than an instruction. So your first deployment of a flow into an environment arrives deactivated, and someone turns it on by hand.
Evidence — the flow before and after each import
Before the first import the environment held no ch67 flow. After it, ch67DailyNote was present as category Modern Flow — the label the environment gives the Category 5 the source sidecar declared — mode Background, statecode and statuscode both Draft, with createdby and ownerid the importing user and a workflowid identical to the id written in that sidecar.verifiedartifact: ch07-flow-import-state · artifacts-ch07.md
The pair a solution declares is a pair the environment has labels for: grouping workflow under a filter on statecode 1 and statuscode 2 returns one row — both columns reading Activated — counting the 242 workflow rows in the lab environment that carry that pair.verifiedartifact: ch07-state-labels · artifacts-ch07.md
A second import of the same solution, with the sidecar changed to declare StateCode 1 and StatusCode 2, left the flow at Draft. A later import of the same source with --activate-plugins also left it at Draft. Neither the state declared in the solution nor the flag that activates plug-ins and workflows turned this flow on.verifiedartifact: ch07-flow-declared-state · artifacts-ch07.md
Connected as skydude@itrancyngergmail.onmicrosoft.com Connected to... skydude's Environment Solution Importing... Solution Imported successfully. Publishing All Customizations... Published All Customizations.The read on either side of this run is in artifacts-ch07.md. Both reads return the same two rows, both Draft.
workflow table taken before and after rather than on this capture.7.7.3Connection references and environment variables
A connection reference is a solution component holding a reference to a connection for a specific connector, and operations within a solution-aware flow bind to the connection reference rather than to a connection. During solution import into a target environment a connection is provided for all the connection references, so that referencing flows can be turned on automatically once the import completes. Flows use connection references for every connector, where canvas apps use them for implicitly shared connections alone.citedsource: Power Apps docs — Use a connection reference in a solution — what it is and what import does with it
Turning a flow on requires the user doing it to own or have permission to use every connection in the flow, and where another user supplied a connection either that user turns the flow on or the connection is shared with the one who does. The error for the failing case is ConnectionAuthorizationFailed. Once the flow has been turned on by the owner of the connections it holds permission to use them, and any co-owner can then turn it off and on.citedsource: Power Apps docs — Use a connection reference in a solution — who may turn the flow on
An environment variable splits into a definition, which holds the key and an optional default value, and a value, which is a separate row and a separate JSON file inside the exported solution zip. A defined value is used even where a default value is also present, and a value cannot exist without a definition. Microsoft's own answer to whether the value belongs in the solution is no: the definition is included and the value is supplied for the target environment during deployment, and the solution import interface prompts for the values of variables that have neither a value nor a default.citedsource: Power Apps docs — Use environment variables in Power Platform solutions — the definition, the value, and import
Caution
Neither a connection reference nor an environment variable was carried into the lab environment as a component. The attempt is recorded in artifacts-ch07.md under ch07-flow-source-shape: the packer rejected hand-authored source for both, naming them as root components it could find no definition for, and an orphaned environmentvariabledefinitions/ folder later made an import fail. The deploy-time behaviour above is cited throughout.
Guidance
Treat the post-import step as part of your deployment and write it down: the connections to bind, the environment variable values to supply, the flows to turn on, and who has to do it. A successful import leaves each of those outstanding, and the claims above say why.