There are many, many posts stating that delegating approvals on the behalf of another user is impossible in Power Automate. In the following article, I'll explain the method to reassign any existing approval without requiring the end user to intervene. I also want to clarify that this won't require you to edit your existing workflows, and you won't have to make weird workarounds to all your existing processes. I am simply showing a flow that behaves identically to the "Reassign" button end users have, but instead letting you do it on anyone's behalf.
Users can already reassign their own requests if you let them. The above example is in the Approval GUI, so I'll show you how to get the equivalent result in the backend.
Now, people are fully correct in saying that there isn't some convenient admin menu to redirect an approval from one user to another. This will be a somewhat complicated process. The upside is that all the complexity is up front. Once the reassignment flow is made, it becomes reuseable for any other Approval in the environment.
Now, in case you didn't know, Approvals are stored in "Dataverse", Microsoft's Database tool that integrates with the Power Platform. There are 3 main Dataverse tables that are relevant to most approvals:
From there, you can infer that "Approval Request" will be the topic of discussion. Whenever an approval is pending, it will have an associated row in "Approval Request". All reassignment does is forward an existing Approval Request to a new Approver. The "Reassign" button makes a new row, and marks the old one as delegated. This upcoming flow will do the exact same thing.
From that, Dataverse is fairly easy to control in Power Automate. That means that even though there isn't an easy button that reassigns other users for us, we can make the database changes required to get the same result. For any power users reading, if you know Dataverse, you'll have no problem skipping to the flow making parts. If you don't know Dataverse, I'd still recommend you be at a moderate Power Automate skill level, but hopefully my instructions will suffice.
Before we continue, I want to highlight the 2 main requirements for this to work.
Before you get intimidated, do note that the 2 above requirements are easier and free if you use Dataverse for Teams. If you are the owner of the Team, you can edit the tables and run the premium connectors needed to make delegation.
So, the first thing you'll need is a trigger. We'll go over some ways to automatically trigger later, but to manually trigger a delegation, you'll need 2 things:
1. The Approval ID of the original Approval. Every Approval has a unique approval ID to identify it. If you used "Create an Approval", one of the Dynamic Content options is Approval ID:
Your other option is to use Dataverse to get the ID. As you'll remember, all Approval info is stored in Dataverse, so if you don't know the approval ID, you can navigate to Approval Table, and simply search for it! In Power Automate, go to
"Tables"
"All Tables"
"Approval" and then Filter by Title. You should be able to find it soon enough.
Once in the Approval Table, you can find all the Approvals in your Org. You may need to unhide some columns to filter them.
2. The second input you'll need is the new User to be delegated to. If the original approver was "John Doe", then you'll need some way to specify that you want John's approval to be delegated to "Jane Doe". For all my examples, I will use UPN, or User Principal Name. As long as the input is fairly unique to an individual person, most identifiers should work instead (if you wish).
I chose "Manually Trigger a Flow", since it allows easy inputs and triggering. Additionally, all screenshots will be in the legacy flow editor, since the new one still has some growing pains, and is currently less efficient for screenshots.
With "Manually Trigger a Flow", you can simply paste your inputs in to delegate.
Next, I use the trim() expression to remove any spaces from the input. This is to be more safe than sorry when it comes to pasting in values.
If you've never seen a scope action before, it lets you group multiple actions into one, like a folder. In this case, I scoped the input trims.
After getting our inputs ready, we need to get the current "Approval Request" row connected to the inputted "Approval ID". This is so that we can deactivate the no longer needed request.
First, we'll make a "List Rows" action from Dataverse. You will choose the "Approval Requests" table. Under advanced, we are going to do an Odata Filter. (If you've done Odata in SharePoint before, it's the same syntax)
The full filter I used is msdyn_flow_approvalrequestidx_approvalid eq '@{outputs('Trim_Inputted_Approval_ID')}'
As you'll notice, just like in SharePoint Odata filters, the column name in the filter is the backend name, not the display name. To get the correct name for a Dataverse column, go to the table in Dataverse, and go to "Edit Column" on the field you want. There, you'll see its Logical name, which works for Odata. For this workflow, I will provide you the column names, but any customizations you make will need the Logical Name trick.
Next up, you likely know that Power Automate will automatically force an "Apply to Each" whenever you use "List Rows" (even if there is only 1 row). To prevent that, we use the expression @{outputs('List_Approval_Requests')?['body/value'][0]?['msdyn_flow_approvalrequestId']} to only get the value of "Request ID" for the returned row. Using [0]?['msdyn_flow_approvalrequestId'] gives me the first row from the List Approval Request values, since the array of values starts at [0]. This lets me skip needing an Apply to Each. You can still make it work with the Apply to Each, but I am prettying it up for maintainability and easier sharing.
@{outputs('List_Approval_Requests')?['body/value'][0]?['msdyn_flow_approvalrequestId']}
After that, we will do the same thing all over again with the "Users" table in Dataverse. This is because our new delegated approval will need ownership over the new request. Ownership in Dataverse is assigned to each user's Dataverse GUID in the "Users" Table. If you input Owner GUID directly, then you won't need the upcoming steps, but I'm assuming that you only know the user's AD UPN, since that's much easier to get for most people.
domainname eq '@{outputs('Trim_Inputted_UPN')}'
@{outputs('List_User_rows')?['body/value'][0]?['systemuserid']}
As you can see, this is basically the same as the last process. The main difference is that I used "Select Columns" this time. That is not necessary, it just makes the output of "List rows" much easier to view by removing unneeded columns. I only need the "systemuserid" since that's what is used to assign approval ownership.
Finally, we have enough setup to actually do the Delegation part! As mentioned earlier, Delegation really is just doing 2 things:
As shown in this screenshot, once reassigned, a connection is made where the old row is connected to the new one via "Reassigned From"
My guess for the reason why this isn't well known is that Power Automate makes it fairly difficult to format the data to do those 2 things. All the previous work was just to get us enough data to make a valid row in "Approval Request". Dataverse gives no wiggle room for bad data, so all the upcoming steps are the most important for success.
The first step (marking that the old pending request is no longer needed) is an "Update a row" action. This is because we are updating the old request to be inactive. For both of the 2 steps, I will go over each parameter individually and provide my exact inputs.
I simply used the dropdown and chose Approval Requests
This is what the "List Approval Requests" action was for, as its value would have been the older approver.
This feature is officially called Reassignment, not delegation, so that's why the status is called that. You can use the dropdown to choose "Reassigned". The Status Reason lets Dataverse know that a delegation occurred, rather than something like a failure.
This lets Dataverse know to no longer wait on this request. You also use the dropdown for this value.
After all that hype, that probably didn't sound so bad. And I agree, cancelling the old request is the easy part. The hard part is coming up next: Adding a new request row.
Just like before, you can use the dropdown to select this.
This is one the most important parameters, and the biggest block in the way of creating this row. "Approval (Approvals)" is a lookup column that connects the "Approvals" table to "Approval Request". However, the weird part of lookups in Dataverse is that when using Power Automate, you need to use the format of SET_NAME(LOOKUP_GUID). Those 2 phrases should look esoteric to you, but that's not your fault.
"SET_NAME" is the backend name for a Dataverse table in its plural, or "set" form. As an example, the display name of one of the tables is "Approval", but its plural is "Approvals". Dataverse uses singular and plural grammar to distinguish from 1 row/table and multiple rows/tables. In the backend, the logical name is "msdyn_flow_approval" and the set name is "msdyn_flow_approvals". However, you can't just always add an "s". Some tables don't add anything to their set name, and some add "es". Just like in English, the plural of "fish" is "fish" or "fishes", but not "fishs". To avoid any issues, simply go to a Dataverse Table > Tools > Copy Set Name. I have to thank this thread and this article for helping me learn this. In this article however, you can trust that all Approval tables are the same, and just use my Set name.
Copy set name lets you not worry about guessing the wrong name. That's important, because every other Dataverse lookup also needs an exact set name to work, Power Automate isn't nice enough to look it up for you.
The next part you need is the unique ID of the row you want to lookup. This "LOOKUP_GUID" often has the same name as the table's display name. As an example, the GUID of the Approval table is a column called "approval". Additionally, that "approval" column is the "Approval ID" we submitted way at the beginning. With that info, you can now see that SET_NAME(LOOKUP_GUID) turns into msdyn_flow_approvals(@{outputs('Trim_Inputted_Approval_ID')})
This is a combination of Approval ID and the type of approval. I'll get into this more later, but the flow I'm showing now is assuming a basic "Approve or Reject" workflow. As such, we just need to add "_BASIC" to the Approval ID. I learned this formatting by simply viewing the Approval Request table before and after a reassignment. I recommend you do the same if you want to fully understand this process.
The name field is normally blank, so I used null. Incase you don't know how to use it, null is an expression for when you want to put nothing into a field, not even "" or a space.
This is the exact same as the default value of this field. Once again, this assumes a normal "Approve"/"Reject" workflow.
This field is normally blank, so I used null. This is not the same column as above, but it does have the same display name. You can use "Peek Code View" to verify that they have different logical names in the backend.
This is a Dropdown that once again we are pre-assuming is a Basic "Approve"/"Reject".
This is a Dropdown that once again we are pre-assuming is a Basic "Approve"/"Reject".
This one is pretty self-explanatory, if the old request is inactive, then it makes sense that the new one is active.
Also self-explanatory, we want to allow reassignment because this is a reassignment.
In Dataverse, the Approval ID Index is an exact copy of the Approval (Approvals) field to make Dataverse queries easier. This is because the Index is a simple text field rather than a lookup, which is easier to transform. Additionally, the rows made by Power Automate have the Approval ID Index in all caps. This is likely not strictly necessary (since you can make Fetch XML Queries case insensitive), but using toUpper() adds negligible complexity to better match the native reassignment process.
Sorry to break up the previous formatting here, but I know that since basically every column gets used here, I couldn't just ignore this column without scaring people. Approval Request is the auto-generated GUID, so it needs no input. For columns that are not handled by Power Automate or not required, I put nothing. A screenshot is attached below.
You could likely input null and not hurt anything, but that adds more steps for no real benefit. For future unneeded columns, I'll just put "No input necessary".
For many upcoming inputs, you will simply use the dynamic content from the previous "Update row". In this example, if the old request was due on 12/12/25, then it makes sense that the new one is due at the same time. Using the dynamic content also helps prevent formatting errors, as Dataverse can't give you typos in its dynamic content.
The logic is the exact same as the above.
Same logic as "Expires On". If you are like me and hoped that this was some hidden reminders feature, I can't figure out how to trigger it. Maybe you can!
This basically means no notifications sent by Teams. Once again, I don't know how to use this weird undocumented feature, but that's a different story than delegation.
This is another Lookup table. This is also the use case of the User GUID. You may notice that we never inputted the name of who this new request is for. The "Owner" column is what decides who needs to approve the request. A quick list below shows what ownership means for Approval tables.
Approval - Owner means who requested an approval
Approval Requests - Owner means who was asked to approve something
Approval Response - Owner means who responded to a request
Like the last Index, this is just a copy of the Owner column. And once again, the uppercase is likely not critical.
Just to mention it real quick, you are allowed to use this Partner Metadata field however you want in all 3 approval tables. If you have a Dynamics 365 Vendor, the column may already be used, but you can use this column to do things like link approvals to SharePoint lists and all sorts of cool stuff. For this article however, nothing is used.
Once again, another lookup. This one lets Dataverse know which inactive request to associate with the new one.
Another Index field, same as the others.
This is a dropdown that once again is assuming a basic request.
Self-explanatory, the new request will be the active one.
Incase you didn't know, Microsoft has pushed a new feature called "steps" in Approvals for Teams. It lets you make 1 approval with tiered approvals, meaning that instead of having to make 4 separate approvals that are made one after another, you can make it so each step needs to be approved one after another in a single approval. It sounds like a great feature! The catch is that it currently isn't automated. Seeing as you now know so much about Dataverse, you can understand why I think steps can also likely automated in Dataverse, but until an official connector is made, you can likely ignore steps and just make them all 0.
This is the last column with data, and it refers to the offset for dates needed for approvals. According to this thread, it's deprecated and is just always 4. However, I would double check if I were you, and see if all your data is also 4.
The entire flow, a more detailed one will be shown at the bottom.
Finally, we are at the end! Shown below is a gif on how the process looks:
For a high-res look at the video and flow, I have uploaded them to Imgur.
So, with the flow made, it's time to talk some caveats and potential uses. There are 2 main caveats that I've seen when using the above process:
Now, for potential uses. The most obvious one is manual delegation. Employee A accidentally sent something to Employee B, so now an admin can delegate it to Employee C.
Before, Employee B would have had to know how to delegate themselves, so manual delegation will improve user experience and simplify your job.
The next potential use is Automatic Delegation. A simple example is an existing workflow now running a child flow that does delegation. With my above design, any premium flow can delegate with just an Approval ID and a UPN by running the above as a child flow. This means that you can do things like if a certain condition is met, delegate to someone new. Another potential use of Automatic Delegation is Approval Forwarding.
Now, I want to preface this by saying that this is just a mockup, and I plan to work on a better article on Approval Forwarding later. But, just to whet your appetite, shown below is an example flow that runs every time an Approval is made in a Dataverse environment. Unless you are getting thousands of Approvals a day, this flow could likely handle being run for every approval, and it would automatically check if someone needs to be delegated. If delegation is needed, it runs my delegation flow. That way, if Employee C is out of office, this flow can automatically run the delegation flow. I recommend trying it out. It's worked well in my testing, and I see no reason why it wouldn't work well in prod, as manual delegation has worked perfectly for me. The two dynamic content you'll see below is "Owner (Value)" to let us delegate specific Owners, and "Approval ID Index", to automatically get the relevant Approval ID.
For a performance boost, I think using "Trigger Conditions" could make this work extremely efficiently. Additionally, do note that to run a manual flow as a child flow, you need to add a "Response" action. That is very easy however.
Thanks for reading! I have written articles on LinkedIn and Reddit in the past, so I plan to share some of them and newer ones here to help the community. - Nigel Smith