Let me clarify the complete picture here because there are multiple permission layers at play.
Permission Set Object-Level Access:
Your sales reps need Read and Edit on the Opportunity object - you have this covered. However, for automated approval flows, you also need the “Submit Records for Approval” system permission in the permission set. This is separate from object CRUD and is specifically required when Flow Builder or other automation submits records. Manual submission by users doesn’t require this permission, which is why your reps can submit manually but the flow fails.
Field-Level Security:
The approval status field issue is critical. Your flow is trying to update this field, but if FLS restricts edit access for sales reps, the flow will fail even in system context when it’s updating user-accessible records. You have three options:
- Grant FLS edit access on the approval status field to the permission set (simplest but may violate security requirements)
- Split the update into two steps: flow updates a staging field (users have edit access), then a trigger in system context updates the restricted field
- Configure the flow to run in system mode without sharing, but this bypasses your sharing rules entirely
Flow Builder Automation:
The flow execution mode matters. Check your flow settings:
Setup → Flows → [Your Flow] → Open
In Flow Properties, verify:
- Run As: System (recommended for approval automation)
- Sharing: With Sharing (maintains record access rules)
For your specific scenario, implement these changes:
- Add system permission to your permission set:
Setup → Permission Sets → Sales_Rep_Permissions
→ System Permissions
→ Enable "Submit Records for Approval"
- For the field-level security conflict, I recommend the staging field approach:
- Create a new field: Approval_Status_Staging__c (grant FLS edit to sales reps)
- Modify your flow to update this staging field instead
- Create an after-update trigger on Opportunity:
trigger OpportunityApprovalSync on Opportunity (after update) {
List<Opportunity> oppsToUpdate = new List<Opportunity>();
for (Opportunity opp : Trigger.new) {
if (opp.Approval_Status_Staging__c != Trigger.oldMap.get(opp.Id).Approval_Status_Staging__c) {
Opportunity oppUpdate = new Opportunity(Id = opp.Id);
oppUpdate.Approval_Status__c = opp.Approval_Status_Staging__c;
oppsToUpdate.add(oppUpdate);
}
}
if (!oppsToUpdate.isEmpty()) {
update oppsToUpdate; // Runs in system context
}
}
- Keep your flow in “Run As System” mode but maintain “With Sharing” to respect record-level access
This approach maintains your security model (users can’t directly edit the approval status field) while allowing the automated flow to function properly. The trigger runs in system context and can update restricted fields regardless of user permissions.
After implementing these changes, test in sandbox first. The “Submit Records for Approval” system permission is the most commonly missed piece in this scenario.
This draft is based on general Salesforce knowledge. It has not been verified against your specific version and environment. Practitioners: verify the steps and share your experience below.