Most approval flows send the request and stop there — leaving list fields stale, requesters in the dark, and history scattered. This end-to-end lesson builds a production-ready Power Automate flow that captures approval outcomes, writes them back to SharePoint, sends tailored notifications, and archives completed records automatically.

Picture this: your organization processes dozens of purchase requests every week through a SharePoint list. Someone submits a request, a manager gets an email, they approve or reject it somewhere in Outlook, and then... nothing. The SharePoint list still shows "Pending." The requester has no idea what happened. The finance team is manually checking the list and emailing people to ask for updates. Someone eventually exports everything to Excel to figure out what's been sitting open for three weeks. Sound familiar?
This is the gap that most partial automation leaves behind — the approval action is automated, but the state management around it is still manual. Power Automate makes it easy to send an approval request. What requires more deliberate design is ensuring that every downstream consequence of that approval — the list item update, the outcome notification, the archive copy — happens automatically, reliably, and in the right order. That's what this lesson is about.
By the end of this lesson, you'll have built a production-ready end-to-end flow that takes a SharePoint list item from submission through approval routing, captures the outcome, updates the list fields in place, sends tailored outcome notifications to the appropriate people, and moves completed records to an archive list — all without a single manual step. You'll also understand why each design decision was made, so you can adapt the pattern to your own use cases.
What you'll learn:
This is an expert-level lesson. You should be comfortable with:
You'll need: Power Automate (standard license sufficient for this pattern), a SharePoint site where you can create and modify lists, and at least one test user account to simulate approvals.
Before touching Power Automate, get your SharePoint list right. The schema you choose now determines what your flow can query, update, and report on later. Most approval tracking problems I've seen trace back to under-designed lists — specifically, lists that capture the request but not the approval event itself.
For this lesson, we'll use a Vendor Contract Request scenario. The organization requires manager approval for any new vendor contract. Here's the schema for the primary list, which we'll call ContractRequests:
| Column Name | Type | Notes |
|---|---|---|
| Title | Single line of text | Auto-populated, used as contract name |
| VendorName | Single line of text | |
| ContractValue | Currency | |
| RequestedBy | Person | Auto-populated from current user |
| RequestedDate | Date and Time | Auto-populated |
| ApprovalStatus | Choice | Pending / Approved / Rejected / Cancelled |
| ApprovalOutcome | Single line of text | Stores approver's comments |
| ApproverName | Person | Who actually approved/rejected |
| ApprovalDate | Date and Time | When the decision was made |
| FlowRunID | Single line of text | Stores the flow run ID for traceability |
| ArchivedDate | Date and Time | Populated when record moves to archive |
| IsArchived | Yes/No | Flag to prevent double-processing |
The ApprovalStatus choice column is your primary lifecycle indicator. Setting it as a Choice rather than a plain text field enforces data integrity and makes filtering and views dramatically easier. The FlowRunID column might look unusual — we'll explain its value when we get to troubleshooting.
Create a second list called ContractRequests_Archive with an identical schema, plus one additional column: OriginalItemID (Number) to preserve the ID of the source record.
Note
You might be tempted to handle archiving by moving the item to a SharePoint document library or a different site. Resist that urge unless you have a specific governance reason. Keeping everything in SharePoint lists within the same site keeps your connector calls cheap, your permissions consistent, and your query patterns simple.
Before building, sketch the flow on paper (or a whiteboard). This one has five conceptually distinct stages, and collapsing them mentally is what causes flows to become unmaintainable spaghetti.
Stage 1 — Trigger and initialization. The flow fires when a new item is created in ContractRequests. We immediately stamp the item with the flow run ID and set initial field values.
Stage 2 — Send for approval. Issue the approval request to the designated approver. This is a blocking action — the flow pauses here until a response arrives or a timeout fires.
Stage 3 — Capture outcome. After the approval resolves, extract the outcome, the approver's response comments, and the timestamp. Store these in variables.
Stage 4 — Update the source list item. Write the outcome back to ContractRequests — updating ApprovalStatus, ApprovalOutcome, ApproverName, and ApprovalDate.
Stage 5 — Notifications and archiving. Send outcome emails to the requester (and optionally the approver). Copy the completed record to ContractRequests_Archive. Set IsArchived to Yes on the original item.
The reason to think in stages is that each stage has a different failure mode and a different recovery concern. If the archive copy fails, you don't want to re-send the notification. If the notification fails, you don't want to roll back the list update. Understanding stage boundaries helps you apply error handling at the right level — a topic we'll touch on in the troubleshooting section, and which you can explore in depth at Master Error Handling and Retry Patterns in Power Automate for Bulletproof Flows.
Create a new automated cloud flow. Choose the trigger "When an item is created" from the SharePoint connector. Configure it with your site URL and the ContractRequests list name.
Tip
If your organization creates items through multiple channels (Power Apps, a form, direct list entry), the "When an item is created" trigger fires for all of them. That's a feature here, not a bug — it means your approval process can't be bypassed just because someone adds a row directly.
Immediately after the trigger, add a "Get item" action — also from the SharePoint connector — using the site URL and list name, with the item ID set to ID from the trigger dynamic content. This might seem redundant, but it has an important purpose: the trigger payload for SharePoint is sometimes incomplete, particularly for Person columns and lookup fields. The "Get item" call fetches the full, expanded record. This protects you from mysterious null values downstream.
Add an "Initialize variable" action for each piece of outcome data you'll capture later. Initialize them before the approval action, because you cannot initialize variables inside a conditional branch.
Add these variables:
varApprovalOutcome — Type: String, Value: (empty)varApprovalComments — Type: String, Value: (empty) varApproverEmail — Type: String, Value: (empty)varApprovalDate — Type: String, Value: (empty)Now add an "Update item" SharePoint action. Use the item ID from the trigger. Set only two fields:
ApprovalStatus: Pending (hardcoding this here means even items created with a wrong default value get normalized)FlowRunID: Use the expression workflow()['run']['name']The expression workflow()['run']['name'] returns the unique GUID for this specific flow run. If the flow fails at any point and someone opens Power Automate run history, they can match the GUID in the list to the exact run. This is the kind of operational detail that makes the difference between a flow that's easy to support and one that's a mystery box.
Key insight
Always stamp the flow run ID early. If a flow runs multiple times against the same item (due to retry logic or manual re-triggering), you can immediately see which run last touched the item. Without this, debugging approval tracking issues becomes an archeological exercise.
Power Automate offers several approval action variants. For this pattern, use "Start and wait for an approval" from the Approvals connector — specifically the "Approve/Reject - First to respond" type (or "Everyone must approve" if you need multi-approver sign-off; see Designing Multi-Stage Approval Chains in Power Automate for multi-approver architectures).
Configure the approval action:
Contract Request: @{triggerOutputs()?['body/Title']} — @{triggerOutputs()?['body/VendorName']}
ManagerEmail column on the list that the requester or a Power App populates. Use that dynamic content value.concat('https://yourtenant.sharepoint.com/sites/yoursite/Lists/ContractRequests/DispForm.aspx?ID=', triggerOutputs()?['body/ID'])
The "Start and wait for an approval" action will wait indefinitely by default. For most business processes, that's a problem — you don't want a request to silently sit for two months because the manager went on leave.
Wrap the approval action in a "Configure run after" pattern or, more practically for this use case, use a parallel branch with a "Delay until" action. However, the cleanest production approach is to use a Do Until loop with a timeout expression — a technique detailed in Automating Time-Sensitive Approval Escalations in Power Automate.
For this lesson, we'll keep the flow focused on the core pattern and accept that the approval may wait. In your production deployment, layer the timeout handling on top.
After "Start and wait for an approval" completes, Power Automate gives you access to a rich response object. The critical pieces are:
Outcome — "Approve" or "Reject" (the string Power Automate returns)Responses — an array of individual approver responses, each containing approverEmail, approverName, comments, and requestDatecompletionDate — when the final decision was madeAdd "Set variable" actions to capture each piece of data:
For varApprovalOutcome:
outputs('Start_and_wait_for_an_approval')?['body/outcome']
For varApprovalComments, pull from the first response in the array:
outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['comments']
For varApproverEmail:
outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['approverEmail']
For varApprovalDate, use the completion date from the approval body:
outputs('Start_and_wait_for_an_approval')?['body/completionDate']
Warning
The responses array is zero-indexed. If you're using "First to respond" with multiple potential approvers assigned, only responses[0] is guaranteed to contain data (it's the first person who responded). If you're using "Everyone must approve," you'll need to loop through the responses array. Don't assume there's always a comments value — an approver can respond without adding comments, so null-guard your comments expression:
if(empty(outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['comments']), 'No comments provided', outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['comments'])
Now you write the approval outcome back to ContractRequests. Add a "Update item" SharePoint action. Use the site URL, the list name, and the item ID from the trigger.
Fields to update:
varApprovalCommentsvarApprovalDateApproverName field using the "Claims" format. In the designer, when you click the person field, Power Automate typically presents a display name field and an email/claims field. Populate the email/claims field with varApproverEmail. SharePoint will resolve the email to a user profile.Add a Condition action checking whether varApprovalOutcome is equal to Approve.
If yes (Approved branch):
ApprovalStatus to ApprovedIf no (Rejected branch):
ApprovalStatus to RejectedYou might wonder: why not do the status update before the condition and just set the status once? You could, using a varStatusToSet variable populated in each branch. Both approaches work. The per-branch update approach is slightly more readable for non-developers maintaining the flow later. The variable approach is more efficient if the update action has side effects (like triggering other flows on item modification). Choose based on your environment. If your SharePoint list has other flows listening for item modifications, prefer the variable approach so you only issue one update.
Tip
Use the Mastering Dynamic Expressions and the Power Automate Formula Language resource to format the ApprovalDate correctly before writing it to SharePoint. SharePoint date-time columns expect ISO 8601 format (e.g., 2024-03-15T09:30:00Z). The completionDate from the approval action is already in this format, but if you're generating a "now" timestamp, use utcNow().
Good outcome notifications are not just "Your request was approved." They give the recipient context, next steps, and a link back to the item. Build two separate email templates — one for approval, one for rejection — using the "Send an email (V2)" action from Office 365 Outlook.
To: The requester's email. SharePoint returns a person column as an object with a Claims, Email, and DisplayName sub-property. Use the expression:
outputs('Get_item')?['body/RequestedBy/Email']
Subject:
✅ Approved: Your Contract Request for @{variables('varVendorName')}
Wait — we haven't initialized varVendorName. You could pull the vendor name directly from the trigger body each time, but wrapping frequently-used trigger values in variables at the start of the flow is a pattern that pays off as the flow grows. Add a varVendorName variable initialized to triggerOutputs()?['body/VendorName'] in your initialization block.
Body (HTML enabled):
<p>Hi @{outputs('Get_item')?['body/RequestedBy/DisplayName']},</p>
<p>Your contract request has been <strong>approved</strong>. Here are the details:</p>
<table>
<tr><td><strong>Contract:</strong></td><td>@{triggerOutputs()?['body/Title']}</td></tr>
<tr><td><strong>Vendor:</strong></td><td>@{variables('varVendorName')}</td></tr>
<tr><td><strong>Value:</strong></td><td>$@{triggerOutputs()?['body/ContractValue']}</td></tr>
<tr><td><strong>Approved by:</strong></td><td>@{outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['approverName']}</td></tr>
<tr><td><strong>Approved on:</strong></td><td>@{formatDateTime(variables('varApprovalDate'), 'dddd, MMMM d, yyyy')}</td></tr>
<tr><td><strong>Comments:</strong></td><td>@{variables('varApprovalComments')}</td></tr>
</table>
<p><a href="@{concat('https://yourtenant.sharepoint.com/sites/yoursite/Lists/ContractRequests/DispForm.aspx?ID=', triggerOutputs()?['body/ID'])}">View your request in SharePoint</a></p>
<p>The contract can now proceed to the next stage. Please contact the procurement team if you have questions.</p>
Mirror the structure but change the messaging:
<p>Hi @{outputs('Get_item')?['body/RequestedBy/DisplayName']},</p>
<p>Your contract request has been <strong>declined</strong>. Please review the feedback below and contact your manager if you'd like to discuss next steps.</p>
<table>
<tr><td><strong>Contract:</strong></td><td>@{triggerOutputs()?['body/Title']}</td></tr>
<tr><td><strong>Vendor:</strong></td><td>@{variables('varVendorName')}</td></tr>
<tr><td><strong>Value:</strong></td><td>$@{triggerOutputs()?['body/ContractValue']}</td></tr>
<tr><td><strong>Rejected by:</strong></td><td>@{outputs('Start_and_wait_for_an_approval')?['body/responses'][0]?['approverName']}</td></tr>
<tr><td><strong>Rejected on:</strong></td><td>@{formatDateTime(variables('varApprovalDate'), 'dddd, MMMM d, yyyy')}</td></tr>
<tr><td><strong>Reason given:</strong></td><td>@{variables('varApprovalComments')}</td></tr>
</table>
<p><a href="@{concat('https://yourtenant.sharepoint.com/sites/yoursite/Lists/ContractRequests/DispForm.aspx?ID=', triggerOutputs()?['body/ID'])}">View your request in SharePoint</a></p>
For deeper control over email templating, composition patterns, and avoiding the most common notification anti-patterns, see Automate Email Notifications with Power Automate.
Note
You can also send a confirmation notification to the approver in both branches — particularly useful for rejected requests, where the approver may want a record of their decision in their inbox. This is optional but recommended for audit-sensitive processes. Keep the approver notification brief: a one-paragraph confirmation with the item link.
Archiving is where many flows cut corners, and where the consequences show up months later when someone needs to report on historical approvals. Do this right.
We're not deleting the original item. We're:
ContractRequests_Archive with all fields populatedIsArchived to Yes on the original ContractRequests itemArchivedDate on the original itemThis approach keeps the operational list clean (you can filter out archived items in your default view) while preserving full history in the archive list. The original item remains queryable for audit purposes. If you need to physically remove items from the active list after a retention period, you can build a separate scheduled flow that hard-deletes items where IsArchived = Yes AND ArchivedDate < [threshold].
For a complete treatment of list item lifecycle operations including deletion, see Automating SharePoint List Item Lifecycle Management with Power Automate.
After your outcome notification action (inside both the Approved and Rejected branches), add a "Create item" SharePoint action. Point it to ContractRequests_Archive.
Map every field:
| Archive Field | Value |
|---|---|
| Title | triggerOutputs()?['body/Title'] |
| VendorName | triggerOutputs()?['body/VendorName'] |
| ContractValue | triggerOutputs()?['body/ContractValue'] |
| RequestedBy | outputs('Get_item')?['body/RequestedBy/Email'] |
| RequestedDate | triggerOutputs()?['body/RequestedDate'] |
| ApprovalStatus | variables('varApprovalOutcome') — but map to the correct choice value |
| ApprovalOutcome | variables('varApprovalComments') |
| ApproverName | variables('varApproverEmail') |
| ApprovalDate | variables('varApprovalDate') |
| FlowRunID | triggerOutputs()?['body/FlowRunID'] |
| OriginalItemID | triggerOutputs()?['body/ID'] |
| ArchivedDate | utcNow() |
Warning
The ApprovalStatus field in your archive list is a Choice column. The value you're storing from the approval outcome is "Approve" or "Reject" — but your Choice column has options "Approved" and "Rejected." These don't match. Either normalize the value with an expression — if(equals(variables('varApprovalOutcome'), 'Approve'), 'Approved', 'Rejected') — or set the choice value explicitly in each branch. This mismatch is one of the most common silent failures in approval tracking flows, because Power Automate doesn't error — it just stores nothing in the Choice column.
After the "Create item" action for the archive, add one more "Update item" action pointing back to ContractRequests:
triggerOutputs()?['body/ID']utcNow()This final update is your audit breadcrumb. Anyone who opens the original record in SharePoint can immediately see when it was archived, and the flow run ID connects it to the specific execution in Power Automate.
At this point, you may have noticed that both the Approved and Rejected branches are doing very similar things — send a notification, create an archive item, update the original item. The only differences are the email template content and the status value. That's a code smell. Here's how to manage it at scale.
Option A: Scope with shared actions after the condition. Put only the status-specific actions inside the condition branches, then move the create-archive and update-original actions outside the condition (after it). Since you've stored the outcome in variables, those subsequent actions just read the variables and don't need to know which branch ran.
Option B: Per-branch self-contained logic. Keep both branches fully self-contained. This is more verbose but easier to read for non-technical stakeholders and simpler to test independently.
For a two-outcome flow like this one, Option A is the clean choice. For flows with three or more outcomes (Approved / Rejected / More Information Needed / Cancelled), the variable-driven approach becomes essential.
Here's what the refactored post-condition sequence looks like:
varFriendlyStatus = "Approved", send approval emailvarFriendlyStatus = "Rejected", send rejection emailvarFriendlyStatus and all other variables)IsArchived = Yes and ArchivedDate = utcNow()This structure is easy to extend. If you later add a "More Information Needed" outcome, you add a nested condition inside the "No" branch and another variable assignment — the archive and update logic stays unchanged.
Key insight
Variables are the connective tissue of maintainable multi-branch flows. Rather than duplicating action configurations across branches, use branches only to set variables, then let a single set of downstream actions consume those variables. This pattern keeps your flow's action count low, which matters for both performance and the Power Automate plan consumption limits on licensed environments.
People columns in SharePoint have a quirk that trips up many flow builders. When you use the trigger output for a Person column, you get the person's Claims string — something like i:0#.f|membership|user@contoso.com. When you use the "Get item" action output, you get a richer object:
{
"DisplayName": "Sarah Chen",
"Email": "sarah.chen@contoso.com",
"Claims": "i:0#.f|membership|sarah.chen@contoso.com"
}
Always use the "Get item" output for Person columns, not the trigger output. When populating a Person column on the archive list via "Create item", you typically provide the email address and Power Automate resolves it. When displaying a person's name in a notification email, use DisplayName.
One edge case: if the requester is an external guest user, their Claims format is different and their Email property may be their external address. Test your flow with both internal and external users if your process accepts requests from outside the organization.
Your end-to-end flow now has eight to twelve sequential actions across two branches. That's enough surface area for intermittent failures to cause real problems. Without error handling, a transient SharePoint API timeout will cause the entire flow to fail — after the approval has already been captured — meaning the outcome is known but the list update and archive never ran.
The minimum error handling you should add:
Wrap the archive creation in a scope action. Name it "Archive Completed Record." Configure the scope's "Configure run after" to run even if previous actions failed (is successful, has failed, has timed out, is skipped). Inside the scope, check that the approval outcome variables are populated before attempting the archive. This prevents the archive from running with incomplete data.
Add a parallel branch on the main flow for failure notifications. Add a parallel branch that runs only on flow failure (configure run after: "has failed"). Inside this branch, add a "Send an email" action to a flow administrator address summarizing what happened, the item ID, and the flow run ID. This is your canary — you'll know immediately when the flow fails rather than discovering it when a requester asks why their contract is still showing "Pending."
Build this flow in your own environment using the following scenario. This exercise is designed to take 60-90 minutes and will leave you with a working, testable artifact.
Scenario: IT Equipment Request approval. An employee submits a request for equipment (laptop, monitor, headset) over $500 via a SharePoint list. A department head must approve. The requester gets notified. Completed requests are archived monthly.
Step 1 — List design. Create a SharePoint list called EquipmentRequests with the following columns: Title (text), EquipmentType (Choice: Laptop / Monitor / Headset / Other), EstimatedCost (Currency), Justification (Multiple lines of text), RequestedBy (Person), RequestedDate (Date), DeptHeadEmail (Single line of text), ApprovalStatus (Choice: Pending / Approved / Rejected), ApprovalComments (Multiple lines of text), ApproverName (Person), ApprovalDate (Date), FlowRunID (Single line), IsArchived (Yes/No), ArchivedDate (Date).
Create a second list called EquipmentRequests_Archive with the same schema plus OriginalItemID (Number).
Step 2 — Build the initialization block. Trigger on item creation. Add "Get item." Initialize four variables: varOutcome, varComments, varApproverEmail, varApprovalDate. Update the item to stamp ApprovalStatus = Pending and FlowRunID = workflow()['run']['name'].
Step 3 — Add the approval action. Send to DeptHeadEmail from the item. In the details, include all five request fields formatted clearly. Add a direct link to the item.
Step 4 — Capture outcome. Set all four variables from the approval response.
Step 5 — Build the condition. Check varOutcome equals "Approve." In each branch, set a varFriendlyStatus variable and send the appropriate notification email to RequestedBy/Email.
Step 6 — Add post-condition archive actions. Create archive item in EquipmentRequests_Archive mapping all fields. Update original item with IsArchived = Yes and ArchivedDate = utcNow().
Step 7 — Test. Create a test item with your own email as DeptHeadEmail. Approve the request. Verify: status updated to Approved in the source list, email received, archive item created, IsArchived set to Yes. Then test the rejection path.
Extension challenge: Add a third branch for when the EstimatedCost is below $500 — skip the approval entirely, auto-approve, and still run the notification and archive logic. Hint: use a condition before the approval action. If cost is under threshold, skip straight to outcome processing using variables.
Cause: The flow is using the trigger body ID (triggerOutputs()?['body/ID']) but the update action is pointing to the wrong list — perhaps the archive list by copy-paste error.
Fix: Double-check every "Update item" action. Look at the list name field, not just the ID field. A mismatch here causes a new item to be created in the wrong list rather than updating the correct one (Power Automate will error if the ID doesn't exist, but if IDs happen to match, you'll update the wrong record).
Cause: Most often, the variable was never set because the "Set variable" action ran but the expression returned null. This happens silently.
Fix: Open the run history for a failed or empty run. Click into the "Set variable" actions and examine the Inputs panel — it shows you exactly what expression was evaluated and what value was stored. If the value is null, your expression path is wrong. The most common culprit is mismatched action names: if you renamed the approval action after referencing it, the dynamic content reference may still point to the old name.
Cause: The approver is looking for the approval in Power Automate's Approvals center (https://make.powerautomate.com > Approvals), but the environment where the flow runs is different from their default environment.
Fix: Direct approvers to the correct environment URL, or link them to the Outlook actionable message (the approve/reject buttons embedded in the email itself). The actionable message works regardless of environment because it calls the approval API directly.
Cause: SharePoint's People column resolution requires the email to be an exact match to a user in your tenant's Azure AD. External emails, service accounts, or typos in DeptHeadEmail will result in an unresolvable person field — Power Automate won't error, it just stores nothing.
Fix: Add an "HTTP" action (or use the Microsoft Graph connector) to validate the approver's email against Azure AD before creating the archive item. If the lookup fails, fall back to storing the email as a plain text value in a separate ApproverEmail text column.
Cause: Your update action (setting ApprovalStatus = Pending and FlowRunID) triggers other flows that listen on "When an item is modified" — and possibly this very flow if you also have a "When an item is modified" trigger elsewhere.
Fix: Add a trigger condition to your flow that checks triggerOutputs()?['body/ApprovalStatus/Value'] is null or empty (meaning it's a truly new item with no status set). In the trigger configuration, under "Settings," add a trigger condition:
@equals(triggerOutputs()?['body/ApprovalStatus/Value'], null)
This prevents re-triggering when the flow itself updates the item.
Warning
The IsArchived flag is your last line of defense against double-archiving. Before adding the archive item, add a condition checking that IsArchived is not already Yes on the source item. Retrieve the current value with a "Get item" action right before the archive step (not the cached one from the beginning of the flow, which was fetched before any updates). If IsArchived is already Yes, skip the archive action.
Cause: Approvers sometimes paste content with smart quotes, em-dashes, or HTML-special characters (<, >, &) into the comments field. When you embed this directly in HTML email body, it can corrupt the layout or trigger Outlook's security filters.
Fix: Pass comments through the encodeUriComponent() function before embedding in HTML, or strip HTML tags using a replace expression chain. For the email body specifically, use replace(variables('varApprovalComments'), '<', '<') as a minimum sanitization step.
You've built something genuinely useful here. The pattern you've implemented — trigger, initialize, approve, capture, update, notify, archive — is the backbone of virtually every production approval workflow in Microsoft 365. More importantly, you've built it with intention: variables as connective tissue, explicit field mapping to avoid silent nulls, archive-not-delete for auditability, and a flow run ID stamp for operational observability.
The design decisions covered in this lesson compound as your flow grows. Keeping archiving separate from notification, normalizing choice values before writing to lists, validating person columns before creating records — each of these is a decision that saves you from debugging sessions at midnight when the finance team needs to know why six contract approvals don't appear in the archive.
Where to go from here:
If your approval process involves multiple approvers in sequence or parallel, see Designing Multi-Stage Approval Chains in Power Automate for architectural patterns that scale beyond two-party sign-off.
If you need the approver to see richer contextual data in the approval card itself — attached documents, table comparisons, dropdown inputs — explore Implementing Adaptive Card-Based Human-in-the-Loop Approvals in Power Automate.
If your requests have urgency tiers and need escalation when an approval sits unanswered for 24 or 48 hours, Automating Recurring Approval Reminders in Power Automate covers the reminder and escalation pattern you'll need.
To connect this approval flow to Planner tasks (so approved contracts automatically generate procurement tasks), see Automating Planner Task Creation, Assignment, and Status Updates with Power Automate.
The end-to-end flow you built today is a foundation, not a ceiling. Approval status tracking is one of those problems that looks simple on the surface and reveals its complexity the moment you put it in front of real users, real data, and real organizational exceptions. You now have both the working pattern and the conceptual framework to handle what comes next.