
Picture this: Your organization processes dozens of purchase requests daily, each requiring approval from department managers before moving to finance. Currently, these requests bounce around email threads, get lost in busy inboxes, and create bottlenecks that delay critical business operations. Meanwhile, your finance team has no visibility into pending approvals, and requesters have no idea where their submissions stand in the process.
This scenario plays out across organizations worldwide—approval workflows that should streamline operations instead create chaos. Power Automate's approval system transforms these manual, error-prone processes into structured, auditable workflows that provide visibility, enforce business rules, and adapt to your organization's unique requirements.
By the end of this lesson, you'll architect sophisticated approval workflows that handle complex routing logic, integrate with multiple data sources, and provide comprehensive audit trails. We'll build a real-world purchase requisition system that demonstrates advanced patterns you can adapt to any approval scenario.
What you'll learn:
You should have experience creating basic Power Automate flows, understand SharePoint list operations, and be comfortable with Power Automate expressions and functions. Familiarity with JSON structure and basic HTTP concepts will help when we explore advanced integration patterns.
Power Automate's approval system operates on a sophisticated infrastructure that goes far beyond simple yes/no decisions. The system maintains approval metadata, manages state transitions, and provides APIs for custom integrations—but understanding its internal workings is crucial for building robust workflows.
When you create an approval, Power Automate generates a unique approval ID and stores metadata in Microsoft's Common Data Service. This metadata includes approval status, assigned approvers, response history, and custom properties you define. The system automatically handles approval notifications through multiple channels (email, Teams, mobile app) and maintains a persistent approval center where users can review pending requests.
The approval object itself contains several critical properties that influence workflow behavior:
{
"id": "approval-guid-here",
"title": "Purchase Request - $15,000 Software License",
"details": {
"requestor": "john.smith@company.com",
"amount": 15000,
"department": "IT",
"vendor": "Microsoft Corporation"
},
"status": "Pending",
"approvers": ["manager@company.com"],
"responses": [],
"createdTime": "2024-01-15T10:30:00Z",
"completedTime": null
}
The status field drives workflow logic and can contain values like "Pending," "Approved," "Rejected," or "Canceled." Understanding these states is crucial because approval conditions evaluate differently based on current status, and parallel approvals require all approvers to respond before the workflow continues.
Real-world approval workflows rarely follow simple linear paths. Purchase requests might require different approvers based on amount thresholds, department policies, or vendor relationships. Let's build a sophisticated routing engine that handles these complexities.
Consider a purchase requisition system with these business rules:
Here's how we implement this logic using Power Automate's condition and switch controls:
Initialize variable: ApprovalStage (String) = "Department"
Initialize variable: RequiredApprovers (Array) = []
Switch on: triggerOutputs()?['body/Amount']
Case 1: less(triggerOutputs()?['body/Amount'], 1000)
Append to array: RequiredApprovers
Value: triggerOutputs()?['body/DepartmentManager']
Case 2: and(
greaterOrEquals(triggerOutputs()?['body/Amount'], 1000),
less(triggerOutputs()?['body/Amount'], 10000)
)
Append to array: RequiredApprovers
Value: [
triggerOutputs()?['body/DepartmentManager'],
"finance-approver@company.com"
]
Case 3: greaterOrEquals(triggerOutputs()?['body/Amount'], 10000)
Append to array: RequiredApprovers
Value: [
triggerOutputs()?['body/DepartmentManager'],
"finance-approver@company.com",
"vp-operations@company.com"
]
This approach scales better than nested conditions because it's easier to modify approval rules without restructuring the entire workflow. However, we need additional logic to handle cross-cutting concerns like IT security reviews:
Condition: and(
equals(triggerOutputs()?['body/Category'], 'IT'),
greater(triggerOutputs()?['body/Amount'], 5000)
)
If yes:
Append to array: RequiredApprovers
Value: "it-security@company.com"
The real power emerges when we make approver assignment dynamic. Instead of hardcoding email addresses, we can query organizational data to find the appropriate approvers:
Get department manager:
HTTP Request to: /_api/web/lists/getbytitle('Department Directory')/items
Filter: DepartmentName eq '@{triggerOutputs()?['body/Department']}'
Select: ManagerEmail
Get finance approver by region:
HTTP Request to: /_api/web/lists/getbytitle('Finance Teams')/items
Filter: and(
Region eq '@{triggerOutputs()?['body/RequestorRegion']}',
Active eq true
)
Select: ApproverEmail
This pattern ensures approval routing remains accurate even as organizational structure changes, and it enables sophisticated rules like regional finance approvers or department-specific procurement policies.
Power Automate's default approval interface works for simple scenarios, but enterprise workflows require custom forms with validation rules, conditional fields, and rich data presentation. We'll build a purchase requisition form that adapts based on user input and enforces business rules.
The approval details section accepts JSON-formatted data, giving us complete control over form layout and content. Here's a sophisticated approach that creates a dynamic form structure:
{
"requestDetails": {
"requestId": "PR-2024-0156",
"requestor": {
"name": "John Smith",
"email": "john.smith@company.com",
"department": "IT Infrastructure",
"costCenter": "CC-4421"
},
"purchase": {
"description": "Microsoft Office 365 E5 licenses for development team",
"justification": "Required for advanced compliance features and Power BI Pro access",
"amount": 15840.00,
"currency": "USD",
"vendor": {
"name": "Microsoft Corporation",
"approved": true,
"contract": "MSA-2023-001"
},
"category": "Software License",
"urgent": false,
"recurring": true,
"renewalDate": "2025-01-15"
},
"approvalHistory": [],
"attachments": [
{
"name": "Quote-MS-O365-2024.pdf",
"url": "https://company.sharepoint.com/sites/procurement/documents/Quote-MS-O365-2024.pdf"
}
]
},
"formConfig": {
"showBudgetImpact": true,
"requireJustification": true,
"allowCounterOffer": false
}
}
This structure enables rich form rendering while maintaining data integrity. The approver sees formatted information instead of raw field values, and the formConfig section controls which form elements appear based on approval context.
For advanced validation, we can implement server-side checks before creating the approval. This prevents invalid requests from entering the workflow:
Validate purchase request:
Condition: Budget validation
Expression: greater(
sub(
body('Get_department_budget')?['AnnualBudget'],
body('Get_department_spending')?['YTDSpending']
),
triggerOutputs()?['body/Amount']
)
If budget insufficient:
Send notification to requestor
Terminate workflow
Condition: Vendor validation
Expression: contains(
body('Get_approved_vendors')?['value'],
triggerOutputs()?['body/VendorName']
)
If vendor not approved:
Create approval: Vendor approval required
Wait for approval response
If vendor approval rejected:
Terminate workflow
This validation layer ensures that only legitimate requests reach approvers, reducing noise and preventing policy violations from propagating through the approval chain.
Enterprise approval workflows often require both sequential stages (where one approval must complete before the next begins) and parallel approvals (where multiple approvers can respond simultaneously). Power Automate handles both patterns, but combining them requires careful orchestration.
Let's implement a complex workflow where:
Here's the sequential portion:
Stage 1 - Department Manager Approval:
Create an approval:
Title: Purchase Request Approval - Stage 1 of 3
Assigned to: @{variables('DepartmentManager')}
Details: @{variables('ApprovalDetails')}
Wait for an approval:
Approval ID: @{body('Create_Stage1_approval')?['name']}
Switch on approval outcome:
Case: Approve
Update SharePoint item: Status = "Department Approved"
Continue to next stage
Case: Reject
Update SharePoint item: Status = "Rejected by Department"
Send rejection notification
Terminate workflow
The parallel approval stage requires more sophisticated handling because we must wait for all approvers to respond before proceeding:
Stage 2 - Parallel Finance and IT Security Approval:
Create Finance approval:
Title: Purchase Request Approval - Finance Review
Assigned to: @{variables('FinanceApprover')}
Details: @{variables('ApprovalDetails')}
Create IT Security approval:
Title: Purchase Request Approval - Security Review
Assigned to: @{variables('ITSecurityApprover')}
Details: @{variables('ApprovalDetails')}
Wait for Finance approval:
Approval ID: @{body('Create_Finance_approval')?['name']}
Wait for IT Security approval:
Approval ID: @{body('Create_ITSecurity_approval')?['name']}
Condition: Both approvals successful
Expression: and(
equals(body('Wait_for_Finance_approval')?['outcome'], 'Approve'),
equals(body('Wait_for_ITSecurity_approval')?['outcome'], 'Approve')
)
This approach works but has a critical limitation: if one approver rejects the request, the workflow still waits for the other approver to respond. For better user experience, we need early termination logic:
Apply to each: Parallel approvals array
Switch on approval outcome:
Case: Approve
Update tracking variable: ApprovedCount + 1
Case: Reject
Update SharePoint item: Status = "Rejected"
Cancel remaining approvals
Terminate workflow
Condition: All parallel approvals complete
Expression: equals(variables('ApprovedCount'), length(variables('ParallelApprovers')))
If yes: Continue to next stage
The final sequential stage demonstrates conditional approval routing:
Stage 3 - VP Approval (if required):
Condition: Amount exceeds VP threshold
Expression: greater(triggerOutputs()?['body/Amount'], 10000)
If yes:
Create VP approval:
Title: Purchase Request Approval - Executive Review
Assigned to: @{variables('VPApprover')}
Details: @{add(
variables('ApprovalDetails'),
json(concat(
'{"previousApprovals":',
string(variables('ApprovalHistory')),
'}'
))
)}
Wait for VP approval:
Approval ID: @{body('Create_VP_approval')?['name']}
This pattern scales to any number of sequential and parallel stages. The key is maintaining clear state variables that track approval progress and enable proper error handling at each stage.
Static approver assignment breaks down in dynamic organizations where people change roles, take vacations, or delegate authority. Power Automate provides built-in delegation support, but implementing robust approver assignment requires additional logic.
The system automatically handles delegation when users set up delegates in their Outlook settings, but we need fallback mechanisms for when primary approvers are unavailable. Here's a comprehensive approach:
Dynamic Approver Resolution:
Get primary approver:
HTTP Request: /_api/web/siteusers/getbyemail('@{variables('PrimaryApprover')}')?$select=Id,LoginName,Title
Check approver availability:
HTTP Request: /api/v1.0/users/@{variables('PrimaryApprover')}/presence
Condition: Approver unavailable or out of office
If yes:
Get backup approver:
HTTP Request: /_api/web/lists/getbytitle('Approver Matrix')/items
Filter: and(
PrimaryApprover eq '@{variables('PrimaryApprover')}',
Active eq true
)
Select: BackupApprover
Condition: Backup approver available
If yes: Assign to backup
If no: Escalate to department head
For complex organizational hierarchies, we can implement recursive approver lookup:
Get Manager Chain:
Initialize variable: ManagerChain (Array) = []
Initialize variable: CurrentUser (String) = @{triggerOutputs()?['body/RequestorEmail']}
Do until: equals(variables('CurrentUser'), variables('UltimateApprover'))
Get user manager:
HTTP Request: /api/v1.0/users/@{variables('CurrentUser')}/manager
Append to ManagerChain: @{body('Get_user_manager')?['mail']}
Set CurrentUser: @{body('Get_user_manager')?['mail']}
Condition: Manager found
If no: Break from loop
This creates a complete management chain that we can use for escalation policies or multi-level approval requirements. We can then select the appropriate approver based on business rules:
Select Approver by Authority Level:
Switch on required authority level:
Case: "Budget":
Filter ManagerChain where BudgetAuthority >= @{triggerOutputs()?['body/Amount']}
Select first result
Case: "Technical":
Filter ManagerChain where TechnicalAuthority eq true
Select first result
Case: "Procurement":
Get procurement approver for category: @{triggerOutputs()?['body/Category']}
This approach ensures that approvals always reach someone with appropriate authority, even in complex organizational structures or when specific approvers are unavailable.
Approval workflows generate significant business data that organizations need for compliance, process improvement, and operational insights. Power Automate captures basic approval metadata, but building comprehensive audit trails requires additional instrumentation.
Let's create a robust auditing system that captures every interaction:
Initialize Audit Log:
Create SharePoint item in Approval Audit list:
RequestId: @{variables('RequestId')}
Action: "Workflow Started"
Actor: @{triggerOutputs()?['body/RequestorEmail']}
Timestamp: @{utcNow()}
Details: {
"originalRequest": @{triggerOutputs()?['body']},
"workflowVersion": "2.1",
"triggeredBy": "SharePoint List Item Creation"
}
For each approval stage, we log detailed information:
Log Approval Stage:
Update SharePoint item in Approval Audit list:
RequestId: @{variables('RequestId')}
Action: "Approval Created"
Actor: "System"
Timestamp: @{utcNow()}
Details: {
"approvalId": @{body('Create_approval')?['name']},
"assignedTo": @{variables('CurrentApprover')},
"stage": @{variables('CurrentStage')},
"expectedResponseTime": @{addHours(utcNow(), 24)},
"escalationPolicy": @{variables('EscalationPolicy')}
}
When approvers respond, we capture rich context:
Log Approval Response:
Create SharePoint item in Approval Audit list:
RequestId: @{variables('RequestId')}
Action: @{concat('Approval ', body('Wait_for_approval')?['outcome'])}
Actor: @{body('Wait_for_approval')?['responses'][0]['approver']}
Timestamp: @{body('Wait_for_approval')?['responses'][0]['submissionTime']}
Details: {
"approvalId": @{body('Wait_for_approval')?['name']},
"outcome": @{body('Wait_for_approval')?['outcome']},
"comments": @{body('Wait_for_approval')?['responses'][0]['comments']},
"responseTime": @{div(
sub(
ticks(body('Wait_for_approval')?['responses'][0]['submissionTime']),
ticks(body('Create_approval')?['createdTime'])
),
600000000
)},
"deviceInfo": @{body('Wait_for_approval')?['responses'][0]['source']}
}
This audit trail enables sophisticated reporting. We can build Power BI dashboards that show:
For real-time monitoring, we can send metrics to Azure Application Insights:
Send Telemetry:
HTTP POST to Application Insights:
Headers:
Content-Type: application/json
Body: {
"name": "Microsoft.ApplicationInsights.Event",
"time": "@{utcNow()}",
"data": {
"baseType": "EventData",
"baseData": {
"name": "ApprovalWorkflow.StageCompleted",
"properties": {
"requestId": "@{variables('RequestId')}",
"stage": "@{variables('CurrentStage')}",
"outcome": "@{variables('StageOutcome')}",
"processingTimeMinutes": "@{variables('ProcessingTime')}",
"approverType": "@{variables('ApproverType')}"
},
"measurements": {
"amount": @{triggerOutputs()?['body/Amount']},
"responseTimeHours": @{variables('ResponseTimeHours')}
}
}
}
}
This telemetry feeds into operational dashboards that help identify process improvements and ensure SLA compliance.
Real-world approval workflows must handle scenarios where approvers don't respond within expected timeframes, are unavailable, or where business conditions change during the approval process. Power Automate provides timeout capabilities, but robust exception handling requires additional logic.
Let's implement a comprehensive escalation system:
Create Approval with Timeout:
Create an approval:
Title: @{variables('ApprovalTitle')}
Assigned to: @{variables('PrimaryApprover')}
Details: @{variables('ApprovalDetails')}
Wait for approval with timeout:
Approval ID: @{body('Create_approval')?['name']}
Timeout: 24 hours
Switch on wait outcome:
Case: "TimedOut"
Log timeout event
Initiate escalation procedure
Case: "Approved"
Continue workflow
Case: "Rejected"
Handle rejection logic
Default:
Log unexpected outcome
Send alert to administrators
The escalation procedure implements a multi-tier strategy:
Escalation Procedure:
Initialize variable: EscalationLevel (Integer) = 1
Initialize variable: MaxEscalations (Integer) = 3
Do until: or(
equals(variables('ApprovalReceived'), true),
greater(variables('EscalationLevel'), variables('MaxEscalations'))
)
Switch on EscalationLevel:
Case 1: Send reminder to original approver
Send reminder email
Wait 4 hours
Case 2: Notify backup approver
Get backup approver from directory
Create new approval for backup approver
Wait 12 hours
Case 3: Escalate to department head
Get department head from organizational chart
Create urgent approval for department head
Notify original requestor of escalation
Wait 8 hours
Increment EscalationLevel
If all escalations exhausted:
Create incident in service management system
Notify workflow administrators
Pause workflow pending manual intervention
Exception scenarios require special handling. For example, what happens if a high-value request is submitted just before month-end budget freeze?
Check Business Rules:
Get current business period:
HTTP Request: /_api/web/lists/getbytitle('Business Calendar')/items
Filter: and(
le(PeriodStart, '@{utcNow()}'),
ge(PeriodEnd, '@{utcNow()}')
)
Condition: Budget freeze period
Expression: equals(body('Get_current_business_period')?['value'][0]['BudgetFreeze'], true)
If yes:
Condition: Amount exceeds freeze threshold
Expression: greater(
triggerOutputs()?['body/Amount'],
body('Get_current_business_period')?['value'][0]['FreezeThreshold']
)
If yes:
Create exception approval:
Title: "BUDGET FREEZE EXCEPTION - " + @{variables('ApprovalTitle')}
Assigned to: CFO and CEO
Details: @{variables('ExceptionApprovalDetails')}
Urgency: High
For critical system failures during approval processing, we implement graceful degradation:
Error Handling:
Try:
Execute normal approval logic
Catch:
Log error details:
Error: @{actions('Primary_approval')?['error']}
RequestId: @{variables('RequestId')}
Timestamp: @{utcNow()}
Switch on error type:
Case: "Service Unavailable"
Queue request for retry
Send notification: "Approval system temporarily unavailable"
Case: "User Not Found"
Resolve approver using backup directory
Create approval with resolved approver
Case: "Permission Denied"
Create service desk ticket
Route to manual approval process
Default:
Create critical incident
Notify system administrators
Route to emergency approval process
This comprehensive exception handling ensures that approval workflows continue functioning even when individual components fail or business conditions change unexpectedly.
Modern approval workflows don't operate in isolation—they integrate with ERP systems, financial platforms, project management tools, and compliance systems. These integrations require sophisticated data mapping, error handling, and synchronization logic.
Let's build an integration with an SAP procurement system that synchronizes approved purchase requests:
SAP Integration:
Condition: Approval fully completed
If yes:
Compose SAP payload:
Value: {
"PurchaseRequisition": {
"RequisitionType": "@{variables('SAPRequisitionType')}",
"CompanyCode": "@{variables('CompanyCode')}",
"PurchasingOrganization": "@{variables('PurchasingOrg')}",
"PurchasingGroup": "@{variables('PurchasingGroup')}",
"Items": [
{
"Material": "@{triggerOutputs()?['body/MaterialCode']}",
"Quantity": "@{triggerOutputs()?['body/Quantity']}",
"Unit": "@{triggerOutputs()?['body/UnitOfMeasure']}",
"DeliveryDate": "@{triggerOutputs()?['body/RequiredDate']}",
"Plant": "@{variables('Plant')}",
"StorageLocation": "@{variables('StorageLocation')}",
"AccountAssignment": {
"CostCenter": "@{triggerOutputs()?['body/CostCenter']}",
"WBSElement": "@{triggerOutputs()?['body/ProjectCode']}"
}
}
]
}
}
HTTP POST to SAP:
URI: https://sap-system.company.com/sap/api/purchaserequisition/create
Headers:
Authorization: Bearer @{body('Get_SAP_token')?['access_token']}
Content-Type: application/json
X-CSRF-Token: @{body('Get_CSRF_token')?['csrf_token']}
Body: @{outputs('Compose_SAP_payload')}
Parse SAP response:
Content: @{body('HTTP_POST_to_SAP')}
Schema: {
"type": "object",
"properties": {
"PurchaseRequisition": {
"type": "string"
},
"Status": {
"type": "string"
},
"Messages": {
"type": "array"
}
}
}
Update SharePoint with SAP PR number:
ID: @{triggerOutputs()?['body/ID']}
SAPPurchaseRequisition: @{body('Parse_SAP_response')?['PurchaseRequisition']}
SAPStatus: @{body('Parse_SAP_response')?['Status']}
For financial system integration, we need to handle complex data transformations:
Financial System Integration:
Get chart of accounts mapping:
HTTP Request: /_api/web/lists/getbytitle('Account Mappings')/items
Filter: and(
Department eq '@{triggerOutputs()?['body/Department']}',
Category eq '@{triggerOutputs()?['body/Category']}',
Active eq true
)
Compose financial transaction:
Value: {
"TransactionType": "PURCHASE_COMMITMENT",
"TransactionDate": "@{formatDateTime(utcNow(), 'yyyy-MM-dd')}",
"Reference": "PR-@{triggerOutputs()?['body/ID']}",
"Description": "Purchase commitment - @{triggerOutputs()?['body/Description']}",
"LineItems": [
{
"Account": "@{body('Get_chart_of_accounts_mapping')?['value'][0]['ExpenseAccount']}",
"CostCenter": "@{triggerOutputs()?['body/CostCenter']}",
"Amount": "@{triggerOutputs()?['body/Amount']}",
"Currency": "@{triggerOutputs()?['body/Currency']}",
"TaxCode": "@{variables('TaxCode')}"
},
{
"Account": "@{body('Get_chart_of_accounts_mapping')?['value'][0]['CommitmentAccount']}",
"Amount": "@{mul(triggerOutputs()?['body/Amount'], -1)}",
"Currency": "@{triggerOutputs()?['body/Currency']}"
}
]
}
Project management system integration requires handling project hierarchies and resource allocation:
Project Management Integration:
Condition: Request linked to project
Expression: not(empty(triggerOutputs()?['body/ProjectCode']))
If yes:
Get project details:
HTTP Request: https://pm-system.company.com/api/projects/@{triggerOutputs()?['body/ProjectCode']}
Headers:
Authorization: Bearer @{variables('PMToken')}
Validate project budget:
Expression: greater(
body('Get_project_details')?['RemainingBudget'],
triggerOutputs()?['body/Amount']
)
If budget sufficient:
Create budget reservation:
ProjectCode: @{triggerOutputs()?['body/ProjectCode']}
Amount: @{triggerOutputs()?['body/Amount']}
Category: @{triggerOutputs()?['body/Category']}
ReservationDate: @{utcNow()}
ExpirationDate: @{addDays(utcNow(), 30)}
If budget insufficient:
Create budget exception request:
ProjectManager: @{body('Get_project_details')?['ProjectManager']}
RequiredAmount: @{triggerOutputs()?['body/Amount']}
AvailableBudget: @{body('Get_project_details')?['RemainingBudget']}
These integration patterns demonstrate how approval workflows become the orchestration layer for complex business processes spanning multiple systems.
Let's build a complete purchase requisition workflow that demonstrates all the concepts we've covered. This exercise creates a real-world system you can adapt for your organization.
Step 1: Create the SharePoint Foundation
First, create a SharePoint list called "Purchase Requisitions" with these columns:
Create a second list called "Approver Matrix" with these columns:
Step 2: Build the Core Workflow
Create a new automated flow triggered when an item is created in the Purchase Requisitions list:
1. Initialize Variables:
- RequestId (String): "PR-" + formatDateTime(utcNow(), 'yyyy') + "-" + triggerOutputs()?['body/ID']
- ApprovalStages (Array): []
- CurrentStage (Integer): 1
- ApprovalHistory (Array): []
2. Validate Request:
Condition: Amount greater than 0
If no:
Update item Status to "Invalid - Amount must be greater than 0"
Terminate
Condition: Required date in future
If no:
Update item Status to "Invalid - Required date must be in future"
Terminate
3. Determine Approval Path:
Get items from Approver Matrix:
Filter: and(
Department eq '@{triggerOutputs()?['body/Department']}',
Category eq '@{triggerOutputs()?['body/Category']}',
le(MinAmount, @{triggerOutputs()?['body/Amount']}),
ge(MaxAmount, @{triggerOutputs()?['body/Amount']})
)
Compose approval stages based on matrix results
Step 3: Implement Dynamic Approver Assignment
4. Resolve Approvers:
Apply to each: Approval stages
Get primary approver availability:
HTTP: /api/v1.0/users/@{item()?['PrimaryApprover']}/presence
Condition: Approver available
If yes:
Set current approver to primary
If no:
Set current approver to backup
Add escalation note to approval history
Step 4: Create Multi-Stage Approval Logic
5. Execute Approval Stages:
Apply to each: Resolved approval stages
Create approval:
Title: "Purchase Requisition - Stage @{variables('CurrentStage')}"
Assigned to: @{item()?['Approver']}
Details: {
"requestId": "@{variables('RequestId')}",
"requestor": "@{triggerOutputs()?['body/Author/DisplayName']}",
"description": "@{triggerOutputs()?['body/Description']}",
"amount": @{triggerOutputs()?['body/Amount']},
"vendor": "@{triggerOutputs()?['body/Vendor']}",
"justification": "@{triggerOutputs()?['body/Justification']}",
"stage": "@{variables('CurrentStage')}",
"totalStages": @{length(variables('ApprovalStages'))}
}
Wait for approval:
Approval ID: @{body('Create_approval')?['name']}
Timeout: 24 hours
Switch on approval outcome:
Case: Approved
Append to ApprovalHistory:
Stage @{variables('CurrentStage')} approved by @{item()?['Approver']}
Increment CurrentStage
Continue to next iteration
Case: Rejected
Update SharePoint item:
Status: "Rejected at Stage @{variables('CurrentStage')}"
ApprovalHistory: @{join(variables('ApprovalHistory'), '; ')}
Send rejection notification
Terminate
Case: TimedOut
Initiate escalation procedure
Step 5: Add Integration and Audit Trail
6. Post-Approval Processing:
Update SharePoint item:
Status: "Approved"
ApprovalHistory: @{join(variables('ApprovalHistory'), '; ')}
Create SAP Purchase Requisition:
[Implementation from integration section]
Update item with SAP PR number:
SAPPurchaseRequisition: @{body('Parse_SAP_response')?['PurchaseRequisition']}
Send approval notification:
To: @{triggerOutputs()?['body/Author/Email']}
Subject: "Purchase Requisition @{variables('RequestId')} Approved"
Body: Include approval summary and next steps
7. Create Comprehensive Audit Log:
For each stage: Log to SharePoint Audit list
Send telemetry to Application Insights
Update management dashboard metrics
Step 6: Test and Validate
Test your workflow with various scenarios:
Monitor the audit trails and verify that all integrations work correctly.
Building robust approval workflows involves navigating several common pitfalls that can derail even well-designed systems. Understanding these issues before they occur saves significant debugging time and prevents user frustration.
Mistake 1: Not Handling Parallel Approval Failures Gracefully
Many developers implement parallel approvals without considering what happens when one approver rejects while another is still reviewing. The workflow continues waiting for all responses, leaving the rejected request in limbo.
Wrong approach:
Wait for Finance approval: [Finance approver]
Wait for IT approval: [IT approver]
Condition: Both approved
Continue workflow
Correct approach:
Apply to each: [Finance approver, IT approver]
Wait for approval with early exit on rejection
Switch on outcome:
Case: Rejected
Cancel remaining approvals
Terminate workflow
Case: Approved
Increment success counter
If success counter equals total approvers:
Continue workflow
Mistake 2: Hardcoding Approver Email Addresses
Hardcoded approvers break when people change roles or leave the organization. Always use dynamic lookup mechanisms.
Wrong: Assigned to: "john.manager@company.com"
Correct:
Get department manager:
Filter user directory by department and role
Assigned to: @{body('Get_department_manager')?['Email']}
Mistake 3: Not Implementing Proper Timeout Handling
Default timeout behavior often leaves workflows in incomplete states. Implement comprehensive escalation procedures.
Mistake 4: Ignoring Approval Delegation
Power Automate handles delegation automatically, but workflows must account for delegate responses in audit trails and notifications.
Mistake 5: Poor Error Messages in Approval Forms
Generic approval forms confuse approvers and lead to delays. Include all relevant context and clear action items.
Troubleshooting Techniques:
When workflows fail, use these diagnostic approaches:
Performance Considerations:
Large organizations may experience performance issues with complex approval workflows:
Security Best Practices:
Approval workflows handle sensitive business data and require careful security consideration:
We've built a sophisticated approval workflow system that handles complex routing logic, integrates with multiple external systems, and provides comprehensive audit trails. The purchase requisition workflow demonstrates patterns you can adapt to any approval scenario—from expense reports and project approvals to contract reviews and system access requests.
The key principles we've established form the foundation for enterprise-grade approval systems:
Architecture Principles:
Technical Patterns:
Business Value: Your approval workflows now provide visibility into process bottlenecks, ensure consistent policy enforcement, and adapt automatically to organizational changes. The audit trails enable data-driven process improvements, while the integration patterns connect approvals to downstream business systems.
Next Steps:
Expand Integration Scope: Connect your approval workflows to additional systems like HR platforms, project management tools, and compliance systems
Implement Advanced Analytics: Build predictive models that identify potential approval delays and recommend process optimizations
Create Approval Templates: Develop reusable workflow templates for different approval scenarios, reducing development time for new processes
Explore Mobile Optimization: Enhance the mobile approval experience with custom Power Apps that provide richer interfaces than default approval notifications
Investigate AI Integration: Implement AI Builder models that can pre-classify requests, predict approval likelihood, or extract data from unstructured documents
The patterns and techniques you've learned apply beyond Power Automate—they represent best practices for any workflow automation platform. As you build more complex approval systems, these foundations will enable you to create processes that truly transform how your organization handles approvals and decision-making.
Learning Path: Flow Automation Basics