
Sf Flow
- 33 installs
- 423 repo stars
- Updated April 27, 2026
- jaganpro/claude-code-sfskills
This is a copy of sf-flow by jaganpro - installs and ranking accrue to the original listing.
Helps with ai & agent building tasks.
About
sf-flow is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- sf-flow
- AI & Agent Building
- AI-coding skill
Sf Flow by the numbers
- 33 all-time installs (skills.sh)
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jaganpro/claude-code-sfskills --skill sf-flowAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 33 |
|---|---|
| repo stars | ★ 423 |
| Last updated | April 27, 2026 |
| Repository | jaganpro/claude-code-sfskills ↗ |
What it does
Helps with ai & agent building tasks.
Files
sf-flow: Salesforce Flow Creation and Validation
Use this skill when the user needs Flow design or Flow XML work: record-triggered, screen, autolaunched, scheduled, or platform-event Flows, including validation, architecture choices, and safe deployment sequencing.
When This Skill Owns the Task
Use sf-flow when the work involves:
.flow-meta.xmlfiles- Flow Builder architecture and XML generation
- record-triggered, screen, scheduled, autolaunched, or platform-event flows
- Flow-specific bulk safety, fault paths, and subflow orchestration
Delegate elsewhere when the user is:
- writing Apex-first automation → sf-apex
- creating objects / fields first → sf-metadata
- deploying metadata → sf-deploy
- seeding post-deploy test data → sf-data
---
Required Context to Gather First
Ask for or infer:
- flow type
- trigger object / entry conditions
- core business goal
- whether this is new, refactor, or repair
- target org alias if deployment or validation is needed
- whether related objects / fields already exist
---
Recommended Workflow
1. Choose the right automation tool
Before building, confirm Flow is the right answer rather than:
- formula field
- validation rule
- roll-up summary
- Apex
2. Choose the right Flow type
| Need | Default flow type |
|---|---|
| same-record update before save | before-save record-triggered |
| related-record work / emails / callouts | after-save record-triggered |
| guided UI | screen flow |
| reusable background logic | autolaunched / subflow |
| scheduled processing | scheduled flow |
| event-driven declarative response | platform-event flow |
| AI-evaluated routing (sentiment, intent, tone) | autolaunched with AI Decision element |
3. Start from a template
Prefer the provided assets:
assets/record-triggered-before-save.xmlassets/record-triggered-after-save.xmlassets/screen-flow-template.xmlassets/autolaunched-flow-template.xmlassets/scheduled-flow-template.xmlassets/platform-event-flow-template.xmlassets/ai-decision-template.xmlassets/subflows/
4. Validate against Flow guardrails
Focus on:
- no DML in loops
- no Get Records inside loops
- proper fault paths
- correct trigger conditions
- safe subflow composition
- AI Decision elements not placed inside loops (credit cost per iteration)
- AI Decision prompts include merge field references for data context
5. Hand off deployment and testing
Use:
- sf-deploy for deploy / dry-run
- sf-data for high-volume test data
---
High-Signal Rules
Flow architecture
- before-save for same-record field updates
- after-save for related records, emails, and callouts
- do not loop over
$Record - use subflows when logic becomes wide or repetitive
Bulk safety
- no DML in loops
- no Get Records in loops
- test with 251+ records when bulk behavior matters
- prefer Transform when the job is shaping data, not per-record branching
Error handling
- every data-changing path should have fault handling
- avoid self-referencing fault connectors
- deploy Flows as Draft first when activation risk is non-trivial
---
Output Format
When finishing, report in this order: 1. Flow type and goal 2. Files created or updated 3. Architecture choices 4. Bulk/error-handling notes 5. Deploy/testing next steps
Suggested shape:
Flow: <name>
Type: <flow type>
Files: <paths>
Design: <trigger choice, subflows, key decisions>
Risks: <bulk safety, fault paths, dependencies>
Next step: <dry-run deploy, activate, or test>---
Flow Testing (CLI)
Run Flow tests from the command line without VS Code:
# Run all flow tests
sf flow run test --target-org <alias> --json
# Run tests for a specific flow
sf flow run test --class-names MyFlow --target-org <alias> --json
# Get results for an asynchronous run
sf flow get test --test-run-id <id> --target-org <alias> --jsonFlow tests execute in the org and can take 1-5 minutes. sf flow run test returns a test run ID for asynchronous runs; use sf flow get test to retrieve results later. Always run with --json and use background execution for longer runs.
---
Cross-Skill Integration
| Need | Delegate to | Reason |
|---|---|---|
| create objects / fields first | sf-metadata | schema readiness |
| deploy / activate flow | sf-deploy | safe deployment sequence |
| create realistic bulk test data | sf-data | post-deploy verification |
| create Apex actions / invocables | sf-apex | imperative logic |
| embed LWC in a screen flow | sf-lwc | custom UI components |
| expose Flow to Agentforce | sf-ai-agentscript | agent action orchestration |
---
Reference Map
Start here
- references/flow-best-practices.md
- references/flow-quick-reference.md
- references/orchestration.md
- references/testing-guide.md
Design / orchestration
- references/subflow-library.md
- references/governance-checklist.md
- references/transform-vs-loop-guide.md
- references/orchestration-guide.md
- references/orchestration-parent-child.md
- references/orchestration-sequential.md
- references/orchestration-conditional.md
AI Decision
- references/ai-decision-guide.md
Screen / integration / troubleshooting
- references/form-building-guide.md
- references/integration-patterns.md
- references/lwc-integration-guide.md
- references/agentforce-flow-integration.md
- references/xml-gotchas.md
- references/testing-checklist.md
- references/wait-patterns.md
- assets/
---
Score Guide
| Score | Meaning |
|---|---|
| 88+ | production-ready Flow |
| 75–87 | good Flow with some review items |
| 60–74 | functional but needs stronger guardrails |
| < 60 | unsafe / incomplete for deployment |
<?xml version="1.0" encoding="UTF-8"?>
<!--
AI Decision Element Template
============================
Use "Define with AI (Advanced)" in Flow Builder to let AI evaluate
conditions at runtime instead of manually defined criteria.
Key structure:
- <attributes><type>LlmPrompt</type> on the <decisions> element = Decision Instructions
- <attributes><type>LlmPrompt</type> on each <rules> element = Outcome Instructions
- No <conditions> block needed on AI-evaluated rules
- The default outcome path needs no AI instructions
Requirements:
- API version 62.0+
- Einstein AI credits (consumed per evaluation)
- Include merge field references in prompts so AI has data context
Usage:
Replace {{PLACEHOLDERS}} with your values.
-->
<decisions>
<name>{{DECISION_API_NAME}}</name>
<label>{{Decision Label}}</label>
<locationX>182</locationX>
<locationY>242</locationY>
<!-- Decision Instructions: describes what the AI is deciding -->
<attributes>
<type>LlmPrompt</type>
<value>{{Describe the decision. Include merge field references like {!Get_Record.FieldName} so AI has the data it needs to evaluate. Example: Detect the sentiment on the case based on subject "{!Get_Case.Subject}" and description "{!Get_Case.Description}" as Negative, Positive, Neutral, or Undetermined.}}</value>
</attributes>
<!-- Default outcome: catches everything that doesn't match a named rule -->
<defaultConnector>
<targetReference>{{DEFAULT_PATH_TARGET}}</targetReference>
</defaultConnector>
<defaultConnectorLabel>{{Default Outcome Label}}</defaultConnectorLabel>
<!-- Named outcome: AI routes here when its instructions match -->
<rules>
<name>{{OUTCOME_API_NAME}}</name>
<!-- Outcome Instructions: describes when AI should choose this path -->
<attributes>
<type>LlmPrompt</type>
<value>{{Describe when this outcome applies. Example: The customer sentiment is Negative. The subject or description contains language indicating frustration, anger, dissatisfaction, complaint, or urgency.}}</value>
</attributes>
<conditionLogic>and</conditionLogic>
<connector>
<targetReference>{{OUTCOME_PATH_TARGET}}</targetReference>
</connector>
<label>{{Outcome Label}}</label>
</rules>
<!-- Add more <rules> blocks for additional AI-evaluated outcomes -->
</decisions>
<?xml version="1.0" encoding="UTF-8"?>
<!--
╔═══════════════════════════════════════════════════════════════════════════════╗
║ APEX ACTION TEMPLATE ║
║ Template: apex-action-template.xml ║
║ API Version: 66.0+ ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ PURPOSE: ║
║ Demonstrates how to call @InvocableMethod Apex from a Flow ║
║ ║
║ KEY PATTERNS: ║
║ - actionCalls element with actionType="apex" ║
║ - inputParameters mapping Flow vars → Apex Request ║
║ - outputParameters mapping Apex Response → Flow vars ║
║ - faultConnector for Apex exception handling ║
║ - Decision element for checking success/failure ║
║ ║
║ PREREQUISITES: ║
║ 1. Deploy the Apex class with @InvocableMethod first ║
║ 2. Apex class name = actionName in actionCalls ║
║ 3. @InvocableVariable names must match inputParameters/outputParameters ║
║ ║
║ USAGE: ║
║ Replace {{PLACEHOLDERS}} with actual values ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
-->
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<description>{{FLOW_DESCRIPTION}} - Demonstrates calling @InvocableMethod Apex</description>
<label>{{FLOW_LABEL}}</label>
<processType>AutoLaunchedFlow</processType>
<status>Draft</status>
<!-- ═══════════════════════════════════════════════════════════════════════════
START ELEMENT
Entry point of the Flow
═══════════════════════════════════════════════════════════════════════════ -->
<start>
<locationX>50</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Call_Apex_Action</targetReference>
</connector>
</start>
<!-- ═══════════════════════════════════════════════════════════════════════════
APEX ACTION CALL
This element invokes the @InvocableMethod in your Apex class
KEY ATTRIBUTES:
- actionName: Must match the Apex class name exactly (case-sensitive)
- actionType: "apex" for @InvocableMethod
- inputParameters: Map Flow variables to @InvocableVariable properties in Request class
- outputParameters: Map @InvocableVariable properties in Response class to Flow variables
═══════════════════════════════════════════════════════════════════════════ -->
<actionCalls>
<name>Call_Apex_Action</name>
<label>Call Apex Action</label>
<locationX>176</locationX>
<locationY>158</locationY>
<!-- ACTION CONFIGURATION -->
<actionName>{{ApexClassName}}</actionName>
<actionType>apex</actionType>
<!-- CONNECTORS -->
<connector>
<targetReference>Check_Apex_Result</targetReference>
</connector>
<faultConnector>
<targetReference>Handle_Apex_Fault</targetReference>
</faultConnector>
<!-- INPUT PARAMETERS
Maps Flow variables → Apex @InvocableVariable in Request class
name = @InvocableVariable property name in Apex
value = Flow variable reference or literal -->
<inputParameters>
<name>recordId</name>
<value>
<elementReference>inp_RecordId</elementReference>
</value>
</inputParameters>
<inputParameters>
<name>operationType</name>
<value>
<stringValue>process</stringValue>
</value>
</inputParameters>
<inputParameters>
<name>includeRelated</name>
<value>
<booleanValue>true</booleanValue>
</value>
</inputParameters>
<!-- OUTPUT PARAMETERS
Maps Apex @InvocableVariable in Response class → Flow variables
name = @InvocableVariable property name in Apex
assignToReference = Flow variable to receive the value -->
<outputParameters>
<assignToReference>var_IsSuccess</assignToReference>
<name>isSuccess</name>
</outputParameters>
<outputParameters>
<assignToReference>var_ErrorMessage</assignToReference>
<name>errorMessage</name>
</outputParameters>
<outputParameters>
<assignToReference>var_OutputMessage</assignToReference>
<name>outputMessage</name>
</outputParameters>
<outputParameters>
<assignToReference>out_ProcessedRecordId</assignToReference>
<name>outputRecordId</name>
</outputParameters>
</actionCalls>
<!-- ═══════════════════════════════════════════════════════════════════════════
DECISION: Check Apex Result
Checks if the Apex action succeeded or returned an error
Pattern: Apex returns isSuccess=true/false instead of throwing exceptions
This allows for graceful error handling in Flow
═══════════════════════════════════════════════════════════════════════════ -->
<decisions>
<name>Check_Apex_Result</name>
<label>Check Apex Result</label>
<locationX>374</locationX>
<locationY>158</locationY>
<defaultConnector>
<targetReference>Handle_Apex_Error</targetReference>
</defaultConnector>
<defaultConnectorLabel>Apex Returned Error</defaultConnectorLabel>
<rules>
<name>Is_Success</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>var_IsSuccess</leftValueReference>
<operator>EqualTo</operator>
<rightValue>
<booleanValue>true</booleanValue>
</rightValue>
</conditions>
<connector>
<targetReference>Success_Processing</targetReference>
</connector>
<label>Success</label>
</rules>
</decisions>
<!-- ═══════════════════════════════════════════════════════════════════════════
SUCCESS PATH
Continue flow processing after successful Apex call
═══════════════════════════════════════════════════════════════════════════ -->
<assignments>
<name>Success_Processing</name>
<label>Success Processing</label>
<locationX>242</locationX>
<locationY>350</locationY>
<assignmentItems>
<assignToReference>out_IsSuccess</assignToReference>
<operator>Assign</operator>
<value>
<booleanValue>true</booleanValue>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>out_Message</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>var_OutputMessage</elementReference>
</value>
</assignmentItems>
<!-- Flow ends after success -->
</assignments>
<!-- ═══════════════════════════════════════════════════════════════════════════
ERROR PATH: Apex Returned isSuccess=false
Handle business logic errors returned by Apex
═══════════════════════════════════════════════════════════════════════════ -->
<assignments>
<name>Handle_Apex_Error</name>
<label>Handle Apex Error</label>
<locationX>506</locationX>
<locationY>350</locationY>
<assignmentItems>
<assignToReference>out_IsSuccess</assignToReference>
<operator>Assign</operator>
<value>
<booleanValue>false</booleanValue>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>out_Message</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>var_ErrorMessage</elementReference>
</value>
</assignmentItems>
</assignments>
<!-- ═══════════════════════════════════════════════════════════════════════════
FAULT PATH: Apex Threw Exception
Handle unhandled exceptions from Apex (system errors, governor limits, etc.)
$Flow.FaultMessage contains the exception message
═══════════════════════════════════════════════════════════════════════════ -->
<assignments>
<name>Handle_Apex_Fault</name>
<label>Handle Apex Fault</label>
<locationX>506</locationX>
<locationY>50</locationY>
<assignmentItems>
<assignToReference>out_IsSuccess</assignToReference>
<operator>Assign</operator>
<value>
<booleanValue>false</booleanValue>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>out_Message</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>System Error: </stringValue>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>out_Message</assignToReference>
<operator>Add</operator>
<value>
<elementReference>$Flow.FaultMessage</elementReference>
</value>
</assignmentItems>
</assignments>
<!-- ═══════════════════════════════════════════════════════════════════════════
INPUT VARIABLES
These receive values from the process/system that invokes this Flow
═══════════════════════════════════════════════════════════════════════════ -->
<variables>
<name>inp_RecordId</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<!-- ═══════════════════════════════════════════════════════════════════════════
INTERNAL VARIABLES
Used to hold values between Flow elements
═══════════════════════════════════════════════════════════════════════════ -->
<variables>
<name>var_IsSuccess</name>
<dataType>Boolean</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
</variables>
<variables>
<name>var_ErrorMessage</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
</variables>
<variables>
<name>var_OutputMessage</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
</variables>
<!-- ═══════════════════════════════════════════════════════════════════════════
OUTPUT VARIABLES
These return values to the process/system that invoked this Flow
═══════════════════════════════════════════════════════════════════════════ -->
<variables>
<name>out_ProcessedRecordId</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<variables>
<name>out_IsSuccess</name>
<dataType>Boolean</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<variables>
<name>out_Message</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
</Flow>
Flow Architecture Review Template
Overview
Flow Name: [e.g., RTF_Account_IndustryChange_Orchestrator] Requested By: [Developer Name] Request Date: [YYYY-MM-DD] Target Deployment: [Sandbox / UAT / Production] Review Type: [ ] New Flow [ ] Major Change [ ] Security Review
---
1. Business Context
1.1 Business Requirement
What business problem does this flow solve?
[Describe the problem in 2-3 sentences]
Success Criteria:
- [ ] Criterion 1
- [ ] Criterion 2
- [ ] Criterion 3
1.2 Users Impacted
Who will be affected by this flow?
- User Profiles: [List profiles]
- User Count: [Approximate number]
- User Locations: [Geography, timezone considerations]
1.3 Business Impact
What's the impact if this flow fails?
- [ ] Critical: Blocks core business process
- [ ] High: Significant inconvenience, workaround available
- [ ] Medium: Minor inconvenience, limited scope
- [ ] Low: Nice-to-have, minimal impact
---
2. Technical Design
2.1 Flow Type & Trigger
Type: [ ] Record-Triggered [ ] Screen [ ] Autolaunched [ ] Scheduled [ ] Platform Event
Trigger Details (if applicable):
- Object: [e.g., Account]
- Trigger Type: [ ] Before-Save [ ] After-Save [ ] Before-Delete
- Trigger Conditions: [When does it fire?]
- Expected Volume: [Records per day/hour]
2.2 Architecture Pattern
Which orchestration pattern is used?
- [ ] Standalone: Single flow, no subflows
- [ ] Parent-Child: Orchestrator + multiple children
- [ ] Sequential: Pipeline of dependent steps
- [ ] Conditional: Router to specialized handlers
Architecture Diagram (if complex):
[Draw or describe flow structure]
Parent Flow
├── Child A
├── Child B
└── Child C2.3 Dependencies
What does this flow depend on?
Subflows:
- [ ] Sub_LogError
- [ ] Sub_SendEmailAlert
- [ ] [Other subflows]
Objects & Fields:
- Read: [Object.Field, Object.Field]
- Create: [Object]
- Update: [Object.Field, Object.Field]
- Delete: [Object]
External Integrations:
- [ ] Apex Classes: [List]
- [ ] Platform Events: [List]
- [ ] HTTP Callouts: [Endpoints]
- [ ] None
Custom Metadata/Settings:
- [ ] [List any configuration dependencies]
---
3. Performance & Scalability
3.1 Volume Assessment
Current Data Volume:
- Records processed per day: [Number]
- Records processed per hour (peak): [Number]
- Average records per transaction: [Number]
Future Growth:
- Expected volume in 6 months: [Number]
- Expected volume in 12 months: [Number]
- Growth assumptions: [Describe]
3.2 Governor Limits Analysis
Estimated Resource Usage:
- SOQL Queries: [Number] / 100 limit
- DML Statements: [Number] / 150 limit
- DML Rows: [Number] / 10,000 limit
- CPU Time: [Milliseconds] / 10,000ms limit
- Subflow Depth: [Number] / 50 limit
Bulk Testing:
- [ ] Tested with 200+ records
- [ ] No governor limit errors
- [ ] Performance acceptable (<5 seconds per batch)
- [ ] Simulation mode passed
3.3 Optimization
Performance Optimizations Applied:
- [ ] No DML inside loops
- [ ] Transform element used (instead of loops)
- [ ] SOQL queries minimized
- [ ] Bulkified operations
- [ ] [Other optimizations]
Identified Bottlenecks: [If any, describe and mitigation plan]
---
4. Security & Governance
4.1 Security Context
Running Mode:
- [ ] User Mode (respects FLS/CRUD - default)
- [ ] System Mode with Sharing (bypasses FLS/CRUD but respects sharing)
- [ ] System Mode without Sharing (bypasses all security)
If System Mode, justification: [Why is System Mode required for this use case?]
4.2 Data Access
Sensitive Data Accessed:
- [ ] No sensitive data
- [ ] SSN / Tax ID
- [ ] Credit Card / Payment Info
- [ ] Health Information (PHI)
- [ ] Other: [Describe]
Compliance Requirements:
- [ ] GDPR
- [ ] HIPAA
- [ ] SOX
- [ ] PCI-DSS
- [ ] None
- [ ] Other: [Describe]
4.3 Permission Testing
Testing Completed:
- [ ] Standard User profile
- [ ] Custom restricted profiles
- [ ] Different permission sets
- [ ] Verified FLS respected
- [ ] Verified CRUD respected
Security Review:
- [ ] Security team review completed
- [ ] Reviewed by: [Name]
- [ ] Review date: [YYYY-MM-DD]
---
5. Error Handling & Observability
5.1 Error Handling
Fault Paths:
- [ ] All DML operations have fault paths
- [ ] Fault paths connect to error logging
- [ ] Critical failures stop execution
- [ ] Non-critical failures logged but don't block
Error Logging:
- [ ] Uses Sub_LogError subflow
- [ ] Custom error logging
- [ ] Logs to: [Object/System]
5.2 Monitoring & Alerting
How will errors be detected?
- [ ] Flow_Error_Log__c custom object
- [ ] Debug logs
- [ ] Platform Events
- [ ] Email notifications
- [ ] Slack/Teams alerts
- [ ] Other: [Describe]
Who receives error alerts?
- [Names/Teams/Distribution Lists]
5.3 Debugging
Troubleshooting Plan:
- Primary Contact: [Name/Email]
- Backup Contact: [Name/Email]
- Documentation: [Link to flow documentation]
- Common Issues: [Known issues and solutions]
---
6. Testing & Quality Assurance
6.1 Testing Completed
Unit Testing:
- [ ] All decision paths tested
- [ ] Error scenarios tested
- [ ] Edge cases tested
- [ ] Variable values verified
Integration Testing:
- [ ] Tested with related flows
- [ ] Tested with existing automation
- [ ] End-to-end user acceptance testing
Bulk Testing:
- [ ] 200+ records tested
- [ ] No governor limit errors
- [ ] Performance acceptable
Security Testing:
- [ ] Different profiles tested
- [ ] Permission edge cases tested
6.2 Test Evidence
Test Results Summary: [Link to test results, screenshots, debug logs]
Known Issues: [List any known issues and workarounds]
---
7. Deployment Plan
7.1 Deployment Strategy
Deployment Path:
- [ ] Dev Sandbox → Full Sandbox → UAT → Production
- [ ] Direct to Sandbox
- [ ] Other: [Describe]
Deployment Method:
- [ ] Change Set
- [ ] Metadata API
- [ ] DevOps Center
- [ ] VS Code Deployment
- [ ] Other: [Describe]
Activation Strategy:
- [ ] Deploy as Draft, activate after verification
- [ ] Activate immediately (emergency only)
- [ ] Phased activation (pilot users first)
7.2 Rollback Plan
If issues arise, how to rollback? 1. [Step 1] 2. [Step 2] 3. [Step 3]
Rollback Risk Assessment:
- [ ] Low: Can deactivate with no impact
- [ ] Medium: Deactivation may affect some users
- [ ] High: Difficult to rollback, extensive impacts
7.3 Communication Plan
Who needs to be informed?
- [ ] End users
- [ ] Support team
- [ ] Business stakeholders
- [ ] IT operations
Communication Timeline:
- [X days before]: Announcement
- [Deployment day]: Status updates
- [X days after]: Success confirmation
---
8. Maintenance & Ownership
8.1 Ownership
Technical Owner: [Name/Email] Business Owner: [Name/Email] Team: [Team Name]
8.2 Documentation
Documentation Links:
- Flow Documentation: [Link]
- User Guide: [Link]
- Support Documentation: [Link]
8.3 Maintenance Schedule
Regular Reviews:
- Monthly error log review: [Owner]
- Quarterly performance review: [Owner]
- Annual security review: [Owner]
---
9. Risk Assessment
9.1 Technical Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| [Risk 1] | High/Med/Low | High/Med/Low | [How to mitigate] |
| [Risk 2] | High/Med/Low | High/Med/Low | [How to mitigate] |
| [Risk 3] | High/Med/Low | High/Med/Low | [How to mitigate] |
9.2 Business Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| [Risk 1] | High/Med/Low | High/Med/Low | [How to mitigate] |
| [Risk 2] | High/Med/Low | High/Med/Low | [How to mitigate] |
9.3 Overall Risk Rating
- [ ] Low Risk: Proceed with deployment
- [ ] Medium Risk: Address mitigations before deployment
- [ ] High Risk: Requires additional review/approval
---
10. Review & Approval
10.1 Reviewer Feedback
Technical Reviewer: [Name/Email] Review Date: [YYYY-MM-DD]
Comments: [Feedback from technical reviewer]
Decision:
- [ ] Approved: Ready for deployment
- [ ] Approved with Conditions: [List conditions]
- [ ] Rejected: Requires changes: [List required changes]
---
10.2 Security Reviewer Feedback
(Required if accessing sensitive data or using System Mode)
Security Reviewer: [Name/Email] Review Date: [YYYY-MM-DD]
Comments: [Feedback from security reviewer]
Decision:
- [ ] Approved: No security concerns
- [ ] Approved with Conditions: [List conditions]
- [ ] Rejected: Security issues must be addressed
---
10.3 Architecture Reviewer Feedback
(Required for complex orchestrations or critical business processes)
Architecture Reviewer: [Name/Email] Review Date: [YYYY-MM-DD]
Comments: [Feedback from architecture reviewer]
Decision:
- [ ] Approved: Architecture is sound
- [ ] Approved with Recommendations: [List recommendations]
- [ ] Rejected: Architecture needs redesign
---
10.4 Final Approval
Approver: [Name/Title] Approval Date: [YYYY-MM-DD]
Final Decision:
- [ ] APPROVED FOR DEPLOYMENT
- [ ] REJECTED - REQUIRES CHANGES
Signature: ___________________
Notes: [Any final comments or special instructions]
---
Post-Deployment Review
(Complete 1 week after production deployment)
Deployed Date: [YYYY-MM-DD] Reviewer: [Name] Review Date: [YYYY-MM-DD]
Performance Metrics
- Error count (past 7 days): [Number]
- Average execution time: [Seconds]
- Governor limit warnings: [ ] Yes [ ] No
Issues Encountered
[List any production issues and resolutions]
Lessons Learned
[What went well? What could be improved?]
Follow-up Actions
- [ ] [Action 1]
- [ ] [Action 2]
---
Document Version
Version: 1.0 Last Updated: [YYYY-MM-DD] Template Owner: [Name/Team]
<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<label>{{FLOW_LABEL}}</label>
<description>{{FLOW_DESCRIPTION}}</description>
<processType>AutoLaunchedFlow</processType>
<status>Draft</status>
<!-- Input Variables -->
<variables>
<name>varInputRecordId</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<!-- Output Variables -->
<variables>
<name>varOutputSuccess</name>
<dataType>Boolean</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
<value>
<booleanValue>false</booleanValue>
</value>
</variables>
<variables>
<name>varOutputMessage</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<!-- Internal Variables -->
<variables>
<name>varRecord</name>
<dataType>SObject</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>{{OBJECT_NAME}}</objectType>
</variables>
<!-- Start Element -->
<start>
<locationX>50</locationX>
<locationY>50</locationY>
<connector>
<targetReference>Get_Record</targetReference>
</connector>
</start>
<!-- Get Records -->
<recordLookups>
<name>Get_Record</name>
<label>Get Record</label>
<locationX>50</locationX>
<locationY>150</locationY>
<assignNullValuesIfNoRecordsFound>false</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Decision_Record_Found</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
<field>Id</field>
<operator>EqualTo</operator>
<value>
<elementReference>varInputRecordId</elementReference>
</value>
</filters>
<getFirstRecordOnly>true</getFirstRecordOnly>
<object>{{OBJECT_NAME}}</object>
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<storeOutputAutomatically>true</storeOutputAutomatically>
</recordLookups>
<!-- Decision: Check if Record Found -->
<decisions>
<name>Decision_Record_Found</name>
<label>Record Found?</label>
<locationX>50</locationX>
<locationY>250</locationY>
<defaultConnector>
<targetReference>Set_Error_Message</targetReference>
</defaultConnector>
<defaultConnectorLabel>Not Found</defaultConnectorLabel>
<rules>
<name>Record_Exists</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>Get_Record</leftValueReference>
<operator>IsNull</operator>
<rightValue>
<booleanValue>false</booleanValue>
</rightValue>
</conditions>
<connector>
<targetReference>Process_Record</targetReference>
</connector>
<label>Record Exists</label>
</rules>
</decisions>
<!-- Assignment: Process Record -->
<assignments>
<name>Process_Record</name>
<label>Process Record</label>
<locationX>50</locationX>
<locationY>350</locationY>
<assignmentItems>
<assignToReference>varOutputSuccess</assignToReference>
<operator>Assign</operator>
<value>
<booleanValue>true</booleanValue>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>varOutputMessage</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Record processed successfully</stringValue>
</value>
</assignmentItems>
</assignments>
<!-- Assignment: Set Error Message -->
<assignments>
<name>Set_Error_Message</name>
<label>Set Error Message</label>
<locationX>200</locationX>
<locationY>350</locationY>
<assignmentItems>
<assignToReference>varOutputSuccess</assignToReference>
<operator>Assign</operator>
<value>
<booleanValue>false</booleanValue>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>varOutputMessage</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Record not found</stringValue>
</value>
</assignmentItems>
</assignments>
</Flow>
<?xml version="1.0" encoding="UTF-8"?>
<!--
Bypass Check Decision Pattern (v2.0.0)
Purpose: Reusable decision element pattern for bypassing flow execution during
data loads, migrations, or integration batch processing.
Usage:
1. Create Custom Metadata Type: Flow_Bypass_Settings__mdt
- Field: Bypass_All_Flows__c (Checkbox) - Master kill switch
- Field: [YourFlowApiName]__c (Checkbox) - Per-flow bypass
2. Create a Custom Metadata record named 'Default'
3. Copy this decision element into your flow's start section
4. To bypass: Check the appropriate field in Custom Metadata
- No code deployment needed
- Instant enable/disable
- Can be done by admins
Benefits:
- Data migrations run faster without flow overhead
- Integration batch processing avoids unwanted automation
- Emergency "kill switch" for problematic flows
- No deployment required to toggle
Note: Replace [YourFlowApiName] with your actual flow's API name.
Example: Account_Creation_Handler__c for Account_Creation_Handler flow
-->
<!-- ============================================================ -->
<!-- OPTION 1: Simple Master Bypass Check -->
<!-- Use when you want ONE switch to disable ALL flows -->
<!-- ============================================================ -->
<decisions>
<name>Check_Bypass_Setting</name>
<label>Check Bypass Setting</label>
<locationX>0</locationX>
<locationY>0</locationY>
<defaultConnector>
<!-- Continue to your main flow logic here -->
<targetReference>Your_First_Flow_Element</targetReference>
</defaultConnector>
<defaultConnectorLabel>Continue Flow</defaultConnectorLabel>
<rules>
<name>Bypass_Enabled</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>$CustomMetadata.Flow_Bypass_Settings__mdt.Default.Bypass_All_Flows__c</leftValueReference>
<operator>EqualTo</operator>
<rightValue>
<booleanValue>true</booleanValue>
</rightValue>
</conditions>
<!-- No connector = flow ends immediately (bypassed) -->
<label>Bypass Enabled</label>
</rules>
</decisions>
<!-- ============================================================ -->
<!-- OPTION 2: Per-Flow Bypass Check -->
<!-- Use when you want individual control over each flow -->
<!-- Replace [YourFlowApiName] with your flow's API name -->
<!-- ============================================================ -->
<!--
<decisions>
<name>Check_Flow_Bypass</name>
<label>Check Flow Bypass</label>
<locationX>0</locationX>
<locationY>0</locationY>
<defaultConnector>
<targetReference>Your_First_Flow_Element</targetReference>
</defaultConnector>
<defaultConnectorLabel>Continue Flow</defaultConnectorLabel>
<rules>
<name>Flow_Bypassed</name>
<conditionLogic>or</conditionLogic>
<conditions>
<leftValueReference>$CustomMetadata.Flow_Bypass_Settings__mdt.Default.Bypass_All_Flows__c</leftValueReference>
<operator>EqualTo</operator>
<rightValue>
<booleanValue>true</booleanValue>
</rightValue>
</conditions>
<conditions>
<leftValueReference>$CustomMetadata.Flow_Bypass_Settings__mdt.Default.[YourFlowApiName]__c</leftValueReference>
<operator>EqualTo</operator>
<rightValue>
<booleanValue>true</booleanValue>
</rightValue>
</conditions>
<label>Flow Bypassed</label>
</rules>
</decisions>
-->
<!-- ============================================================ -->
<!-- OPTION 3: User-Based Bypass -->
<!-- Use when specific users (integration users) should bypass -->
<!-- Requires Custom Permission: Bypass_Flows -->
<!-- ============================================================ -->
<!--
<decisions>
<name>Check_User_Bypass</name>
<label>Check User Bypass</label>
<locationX>0</locationX>
<locationY>0</locationY>
<defaultConnector>
<targetReference>Your_First_Flow_Element</targetReference>
</defaultConnector>
<defaultConnectorLabel>Continue Flow</defaultConnectorLabel>
<rules>
<name>User_Has_Bypass</name>
<conditionLogic>or</conditionLogic>
<conditions>
<leftValueReference>$CustomMetadata.Flow_Bypass_Settings__mdt.Default.Bypass_All_Flows__c</leftValueReference>
<operator>EqualTo</operator>
<rightValue>
<booleanValue>true</booleanValue>
</rightValue>
</conditions>
<conditions>
<leftValueReference>$Permission.Bypass_Flows</leftValueReference>
<operator>EqualTo</operator>
<rightValue>
<booleanValue>true</booleanValue>
</rightValue>
</conditions>
<label>User Has Bypass</label>
</rules>
</decisions>
-->
<!-- ============================================================ -->
<!-- SETUP INSTRUCTIONS -->
<!-- ============================================================ -->
<!--
Step 1: Create Custom Metadata Type
- Label: Flow Bypass Settings
- API Name: Flow_Bypass_Settings
- Add fields as needed (all Checkbox type)
Step 2: Create Custom Metadata Record
- Label: Default
- Name: Default
- Set all bypass fields to false (unchecked)
Step 3: (Optional) Create Custom Permission
- Label: Bypass Flows
- API Name: Bypass_Flows
- Assign to integration user profiles
Step 4: Add Decision to Your Flow
- Copy appropriate option above
- Place immediately after Start element
- Update targetReference to your first element
Step 5: To Bypass During Data Load
- Edit Flow_Bypass_Settings__mdt.Default
- Check appropriate bypass field(s)
- Run your data load
- Uncheck bypass field(s) when complete
IMPORTANT: Always uncheck bypass settings after data operations!
-->
<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<!--
Template: Get Records (recordLookups) Patterns v1.0.0
API Version: 66.0+
═══════════════════════════════════════════════════════════════════════════════
RECORDLOOKUPS ELEMENT OPTIONS
═══════════════════════════════════════════════════════════════════════════════
┌──────────────────────────────────┬─────────────────────────────────────────┐
│ Element │ Purpose │
├──────────────────────────────────┼─────────────────────────────────────────┤
│ object │ API name of SObject to query │
│ filters │ WHERE clause conditions │
│ filterLogic │ "and" / "or" / custom (1 AND 2 OR 3) │
│ queriedFields │ SELECT fields (explicit list) │
│ getFirstRecordOnly │ true = single record, false = collection│
│ sortField │ ORDER BY field │
│ sortOrder │ Asc / Desc │
│ limit │ LIMIT clause (max records) │
│ outputReference │ Variable to store result │
│ storeOutputAutomatically │ Auto-create variable (NOT RECOMMENDED) │
│ assignNullValuesIfNoRecordsFound │ Set fields to null on no match │
└──────────────────────────────────┴─────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════════════════════
⚠️ CRITICAL: RELATIONSHIP FIELDS NOT SUPPORTED
═══════════════════════════════════════════════════════════════════════════════
Flow recordLookups CANNOT query parent relationship fields!
❌ FAILS: <queriedFields>Account.Name</queriedFields>
❌ FAILS: <queriedFields>Manager.Email</queriedFields>
✅ SOLUTION: Two-step query pattern (see Pattern 4 below)
═══════════════════════════════════════════════════════════════════════════════
⚠️ SECURITY: AVOID storeOutputAutomatically
═══════════════════════════════════════════════════════════════════════════════
When storeOutputAutomatically="true", ALL fields are retrieved.
This can expose sensitive data (SSN, Salary, etc.) unintentionally.
✅ ALWAYS specify explicit queriedFields + outputReference
═══════════════════════════════════════════════════════════════════════════════
VARIABLE NAMING (v2.0.0):
- var_ for regular variables (e.g., var_RecordCount)
- col_ for collections (e.g., col_Accounts)
- rec_ for record variables (e.g., rec_Account)
- inp_ for input variables (e.g., inp_AccountId)
- out_ for output variables (e.g., out_FoundRecord)
═══════════════════════════════════════════════════════════════════════════════
-->
<apiVersion>66.0</apiVersion>
<description>Template demonstrating all recordLookups patterns: single record, collection, sorting, filtering, and two-step queries.</description>
<label>Get_Records_Patterns</label>
<processType>AutoLaunchedFlow</processType>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 1: GET SINGLE RECORD BY ID
═══════════════════════════════════════════════════════════════════════════════
Most common pattern - fetch one record by its unique identifier.
Equivalent SOQL:
SELECT Id, Name, Industry, Website FROM Account WHERE Id = :inp_AccountId LIMIT 1
-->
<recordLookups>
<name>Get_Account_By_Id</name>
<label>Get Account By Id</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignNullValuesIfNoRecordsFound>true</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Decision_Account_Found</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
<field>Id</field>
<operator>EqualTo</operator>
<value>
<elementReference>inp_AccountId</elementReference>
</value>
</filters>
<!-- Return single record, not collection -->
<getFirstRecordOnly>true</getFirstRecordOnly>
<object>Account</object>
<!-- Explicit variable - RECOMMENDED -->
<outputReference>rec_Account</outputReference>
<!-- Only fetch fields you need - SECURITY BEST PRACTICE -->
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<queriedFields>Industry</queriedFields>
<queriedFields>Website</queriedFields>
</recordLookups>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 2: GET COLLECTION WITH FILTERS
═══════════════════════════════════════════════════════════════════════════════
Fetch multiple records matching criteria.
Equivalent SOQL:
SELECT Id, Name, Amount, StageName FROM Opportunity
WHERE AccountId = :inp_AccountId AND StageName = 'Closed Won'
-->
<recordLookups>
<name>Get_Closed_Opportunities</name>
<label>Get Closed Won Opportunities</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignNullValuesIfNoRecordsFound>false</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Decision_Has_Opportunities</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
<field>AccountId</field>
<operator>EqualTo</operator>
<value>
<elementReference>inp_AccountId</elementReference>
</value>
</filters>
<filters>
<field>StageName</field>
<operator>EqualTo</operator>
<value>
<stringValue>Closed Won</stringValue>
</value>
</filters>
<!-- Return collection, not single record -->
<getFirstRecordOnly>false</getFirstRecordOnly>
<object>Opportunity</object>
<outputReference>col_ClosedOpportunities</outputReference>
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<queriedFields>Amount</queriedFields>
<queriedFields>StageName</queriedFields>
<queriedFields>CloseDate</queriedFields>
</recordLookups>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 3: GET RECORDS WITH SORTING AND LIMIT
═══════════════════════════════════════════════════════════════════════════════
Fetch records with specific order and limit count.
Equivalent SOQL:
SELECT Id, Name, CreatedDate FROM Case
WHERE AccountId = :inp_AccountId AND Status != 'Closed'
ORDER BY CreatedDate DESC
LIMIT 10
-->
<recordLookups>
<name>Get_Recent_Open_Cases</name>
<label>Get Recent Open Cases</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignNullValuesIfNoRecordsFound>false</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Process_Cases</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
<field>AccountId</field>
<operator>EqualTo</operator>
<value>
<elementReference>inp_AccountId</elementReference>
</value>
</filters>
<filters>
<field>Status</field>
<operator>NotEqualTo</operator>
<value>
<stringValue>Closed</stringValue>
</value>
</filters>
<getFirstRecordOnly>false</getFirstRecordOnly>
<!-- LIMIT clause -->
<limit>10</limit>
<object>Case</object>
<outputReference>col_RecentCases</outputReference>
<queriedFields>Id</queriedFields>
<queriedFields>Subject</queriedFields>
<queriedFields>Status</queriedFields>
<queriedFields>Priority</queriedFields>
<queriedFields>CreatedDate</queriedFields>
<!-- ORDER BY field -->
<sortField>CreatedDate</sortField>
<!-- Desc = newest first -->
<sortOrder>Desc</sortOrder>
</recordLookups>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 4: TWO-STEP QUERY (Parent Record Lookup)
═══════════════════════════════════════════════════════════════════════════════
When you need parent fields (e.g., Account.Owner.Email from Contact):
1. Get child record with lookup ID field
2. Query parent record using that ID
This pattern works around Flow's lack of relationship query support.
-->
<!-- Step 1: Get Contact with AccountId (lookup ID only) -->
<recordLookups>
<name>Get_Contact_With_AccountId</name>
<label>Get Contact</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignNullValuesIfNoRecordsFound>true</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Decision_Contact_Has_Account</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
<field>Id</field>
<operator>EqualTo</operator>
<value>
<elementReference>inp_ContactId</elementReference>
</value>
</filters>
<getFirstRecordOnly>true</getFirstRecordOnly>
<object>Contact</object>
<outputReference>rec_Contact</outputReference>
<queriedFields>Id</queriedFields>
<queriedFields>FirstName</queriedFields>
<queriedFields>LastName</queriedFields>
<!-- Get the LOOKUP ID, not the parent fields -->
<queriedFields>AccountId</queriedFields>
</recordLookups>
<!-- Step 2: Get Account using the AccountId from Contact -->
<recordLookups>
<name>Get_Parent_Account</name>
<label>Get Parent Account</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignNullValuesIfNoRecordsFound>true</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Process_With_Account</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
<field>Id</field>
<operator>EqualTo</operator>
<value>
<!-- Reference the lookup ID from step 1 -->
<elementReference>rec_Contact.AccountId</elementReference>
</value>
</filters>
<getFirstRecordOnly>true</getFirstRecordOnly>
<object>Account</object>
<outputReference>rec_ParentAccount</outputReference>
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<queriedFields>Industry</queriedFields>
<queriedFields>OwnerId</queriedFields>
</recordLookups>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 5: CUSTOM FILTER LOGIC (OR conditions)
═══════════════════════════════════════════════════════════════════════════════
Use custom filterLogic for complex conditions.
Equivalent SOQL:
SELECT Id, Name FROM Lead
WHERE (Status = 'Open' OR Status = 'Working') AND LeadSource = 'Web'
filterLogic format: "(1 OR 2) AND 3"
- Numbers correspond to filter order (1-indexed)
-->
<recordLookups>
<name>Get_Active_Web_Leads</name>
<label>Get Active Web Leads</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignNullValuesIfNoRecordsFound>false</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Process_Leads</targetReference>
</connector>
<!-- Custom logic: (filter1 OR filter2) AND filter3 -->
<filterLogic>(1 OR 2) AND 3</filterLogic>
<filters>
<!-- Filter 1 -->
<field>Status</field>
<operator>EqualTo</operator>
<value>
<stringValue>Open - Not Contacted</stringValue>
</value>
</filters>
<filters>
<!-- Filter 2 -->
<field>Status</field>
<operator>EqualTo</operator>
<value>
<stringValue>Working - Contacted</stringValue>
</value>
</filters>
<filters>
<!-- Filter 3 -->
<field>LeadSource</field>
<operator>EqualTo</operator>
<value>
<stringValue>Web</stringValue>
</value>
</filters>
<getFirstRecordOnly>false</getFirstRecordOnly>
<object>Lead</object>
<outputReference>col_ActiveLeads</outputReference>
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<queriedFields>Status</queriedFields>
<queriedFields>LeadSource</queriedFields>
</recordLookups>
<!--
═══════════════════════════════════════════════════════════════════════════════
DECISIONS: Null Checks After Get Records
═══════════════════════════════════════════════════════════════════════════════
ALWAYS check if record was found before using it!
-->
<decisions>
<name>Decision_Account_Found</name>
<label>Account Found?</label>
<locationX>0</locationX>
<locationY>0</locationY>
<defaultConnectorLabel>Not Found</defaultConnectorLabel>
<rules>
<name>Account_Exists</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>rec_Account</leftValueReference>
<operator>IsNull</operator>
<rightValue>
<booleanValue>false</booleanValue>
</rightValue>
</conditions>
<connector>
<targetReference>Process_Account</targetReference>
</connector>
<label>Found</label>
</rules>
</decisions>
<decisions>
<name>Decision_Contact_Has_Account</name>
<label>Contact Has Account?</label>
<locationX>0</locationX>
<locationY>0</locationY>
<defaultConnectorLabel>No Account</defaultConnectorLabel>
<rules>
<name>Has_Account</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>rec_Contact.AccountId</leftValueReference>
<operator>IsNull</operator>
<rightValue>
<booleanValue>false</booleanValue>
</rightValue>
</conditions>
<connector>
<targetReference>Get_Parent_Account</targetReference>
</connector>
<label>Has Account</label>
</rules>
</decisions>
<decisions>
<name>Decision_Has_Opportunities</name>
<label>Has Opportunities?</label>
<locationX>0</locationX>
<locationY>0</locationY>
<defaultConnectorLabel>No Opportunities</defaultConnectorLabel>
<rules>
<name>Has_Opps</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>col_ClosedOpportunities</leftValueReference>
<operator>IsNull</operator>
<rightValue>
<booleanValue>false</booleanValue>
</rightValue>
</conditions>
<connector>
<targetReference>Loop_Opportunities</targetReference>
</connector>
<label>Has Opportunities</label>
</rules>
</decisions>
<!-- Placeholder elements for flow connectivity -->
<assignments>
<name>Process_Account</name>
<label>Process Account</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>var_Result</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>rec_Account.Name</elementReference>
</value>
</assignmentItems>
</assignments>
<assignments>
<name>Process_Cases</name>
<label>Process Cases</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>var_Result</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Cases processed</stringValue>
</value>
</assignmentItems>
</assignments>
<assignments>
<name>Process_Leads</name>
<label>Process Leads</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>var_Result</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Leads processed</stringValue>
</value>
</assignmentItems>
</assignments>
<assignments>
<name>Process_With_Account</name>
<label>Process With Account</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>var_Result</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>rec_ParentAccount.Name</elementReference>
</value>
</assignmentItems>
</assignments>
<loops>
<name>Loop_Opportunities</name>
<label>Loop Opportunities</label>
<locationX>0</locationX>
<locationY>0</locationY>
<collectionReference>col_ClosedOpportunities</collectionReference>
<iterationOrder>Asc</iterationOrder>
<nextValueConnector>
<targetReference>Process_Single_Opportunity</targetReference>
</nextValueConnector>
<noMoreValuesConnector>
<targetReference>Finalize_Processing</targetReference>
</noMoreValuesConnector>
</loops>
<assignments>
<name>Process_Single_Opportunity</name>
<label>Process Single Opportunity</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>var_TotalAmount</assignToReference>
<operator>Add</operator>
<value>
<elementReference>Loop_Opportunities.Amount</elementReference>
</value>
</assignmentItems>
<connector>
<targetReference>Loop_Opportunities</targetReference>
</connector>
</assignments>
<assignments>
<name>Finalize_Processing</name>
<label>Finalize Processing</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>var_Result</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Processing complete</stringValue>
</value>
</assignmentItems>
</assignments>
<!-- Start Element -->
<start>
<locationX>0</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Get_Account_By_Id</targetReference>
</connector>
</start>
<status>Draft</status>
<!--
═══════════════════════════════════════════════════════════════════════════════
VARIABLES
═══════════════════════════════════════════════════════════════════════════════
-->
<!-- Input Variables -->
<variables>
<name>inp_AccountId</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<variables>
<name>inp_ContactId</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<!-- Single Record Variables -->
<variables>
<name>rec_Account</name>
<dataType>SObject</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Account</objectType>
</variables>
<variables>
<name>rec_Contact</name>
<dataType>SObject</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Contact</objectType>
</variables>
<variables>
<name>rec_ParentAccount</name>
<dataType>SObject</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Account</objectType>
</variables>
<!-- Collection Variables -->
<variables>
<name>col_ClosedOpportunities</name>
<dataType>SObject</dataType>
<isCollection>true</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Opportunity</objectType>
</variables>
<variables>
<name>col_RecentCases</name>
<dataType>SObject</dataType>
<isCollection>true</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Case</objectType>
</variables>
<variables>
<name>col_ActiveLeads</name>
<dataType>SObject</dataType>
<isCollection>true</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Lead</objectType>
</variables>
<!-- Processing Variables -->
<variables>
<name>var_Result</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<variables>
<name>var_TotalAmount</name>
<dataType>Currency</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
<scale>2</scale>
</variables>
<!--
═══════════════════════════════════════════════════════════════════════════════
BEST PRACTICES FOR RECORDLOOKUPS
═══════════════════════════════════════════════════════════════════════════════
1. ALWAYS SPECIFY EXPLICIT FIELDS
- Use queriedFields to list only needed fields
- NEVER use storeOutputAutomatically (security risk)
2. ALWAYS CHECK FOR NULL
- Add Decision after Get Records
- Check: rec_Record IsNull = false
- Prevents null pointer errors
3. USE APPROPRIATE getFirstRecordOnly
- true: When expecting single record (by Id, unique field)
- false: When expecting multiple records (by lookup, criteria)
4. OPTIMIZE WITH SORTING AND LIMITS
- Use sortField + sortOrder for ordered results
- Use limit to cap record count
5. AVOID RELATIONSHIP FIELDS
- ❌ Can't use: Account.Name, Owner.Email
- ✅ Use two-step query pattern instead
6. FILTER LOGIC PATTERNS
- "and": All conditions must match
- "or": Any condition can match
- Custom: "(1 AND 2) OR 3" for complex logic
═══════════════════════════════════════════════════════════════════════════════
FILTER OPERATORS REFERENCE
═══════════════════════════════════════════════════════════════════════════════
┌────────────────────┬────────────────────────────────────────────────────────┐
│ Operator │ Description │
├────────────────────┼────────────────────────────────────────────────────────┤
│ EqualTo │ Exact match │
│ NotEqualTo │ Not equal │
│ GreaterThan │ Greater than (numbers, dates) │
│ GreaterThanOrEqual │ Greater than or equal │
│ LessThan │ Less than │
│ LessThanOrEqualTo │ Less than or equal │
│ Contains │ String contains (LIKE '%value%') │
│ StartsWith │ String starts with (LIKE 'value%') │
│ EndsWith │ String ends with (LIKE '%value') │
│ IsNull │ Field is null (true) or not null (false) │
│ IsChanged │ Field changed (Record-Triggered only, with $Record) │
└────────────────────┴────────────────────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════════════════════
-->
</Flow>
<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<!--
Template: Loop Element Patterns v1.0.0
API Version: 66.0+
═══════════════════════════════════════════════════════════════════════════════
LOOP ELEMENT STRUCTURE
═══════════════════════════════════════════════════════════════════════════════
Loops iterate over collections (SObject[], String[], Number[], etc.)
KEY CONNECTORS:
┌─────────────────────────┬────────────────────────────────────────────────┐
│ Connector │ Purpose │
├─────────────────────────┼────────────────────────────────────────────────┤
│ nextValueConnector │ Points to FIRST element INSIDE the loop body │
│ noMoreValuesConnector │ Points to element AFTER loop completes │
└─────────────────────────┴────────────────────────────────────────────────┘
ITERATION ORDER:
- Asc: Process collection from first to last (default)
- Desc: Process collection from last to first
LOOP VARIABLE ACCESS:
- Automatic: Reference loop element name directly (e.g., Loop_Contacts)
- Manual: Use assignNextValueToReference to store in named variable
═══════════════════════════════════════════════════════════════════════════════
CRITICAL: AVOID DML INSIDE LOOPS!
═══════════════════════════════════════════════════════════════════════════════
❌ BAD: Create/Update/Delete Record inside loop = N DML operations
✅ GOOD: Collect records in loop, single DML after loop exits
Pattern:
1. Loop through source collection
2. Inside loop: Add modified record to UPDATE collection
3. After loop (noMoreValuesConnector): Single Update Records element
═══════════════════════════════════════════════════════════════════════════════
VARIABLE NAMING (v2.0.0):
- var_ for regular variables (e.g., var_Counter)
- col_ for collections (e.g., col_Contacts)
- rec_ for record variables (e.g., rec_CurrentContact)
- inp_ for input variables (e.g., inp_AccountId)
- out_ for output variables (e.g., out_ProcessedCount)
═══════════════════════════════════════════════════════════════════════════════
-->
<apiVersion>66.0</apiVersion>
<!--
═══════════════════════════════════════════════════════════════════════════════
ASSIGNMENTS: Initialize variables before loop
═══════════════════════════════════════════════════════════════════════════════
-->
<assignments>
<name>Add_To_Update_Collection</name>
<label>Add To Update Collection</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<!-- Add current record to collection for bulk update -->
<assignToReference>col_RecordsToUpdate</assignToReference>
<operator>Add</operator>
<value>
<elementReference>rec_CurrentRecord</elementReference>
</value>
</assignmentItems>
<assignmentItems>
<!-- Increment counter -->
<assignToReference>var_ProcessedCount</assignToReference>
<operator>Add</operator>
<value>
<numberValue>1.0</numberValue>
</value>
</assignmentItems>
<!-- Loop back to continue iteration -->
<connector>
<targetReference>Loop_Through_Records</targetReference>
</connector>
</assignments>
<assignments>
<name>Initialize_Variables</name>
<label>Initialize Variables</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>var_ProcessedCount</assignToReference>
<operator>Assign</operator>
<value>
<numberValue>0.0</numberValue>
</value>
</assignmentItems>
<connector>
<targetReference>Get_Records</targetReference>
</connector>
</assignments>
<assignments>
<name>Process_Current_Record</name>
<label>Process Current Record</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<!-- Copy loop variable to working record variable -->
<assignToReference>rec_CurrentRecord</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>Loop_Through_Records</elementReference>
</value>
</assignmentItems>
<assignmentItems>
<!-- Modify field on working record -->
<assignToReference>rec_CurrentRecord.Description</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Processed by Flow</stringValue>
</value>
</assignmentItems>
<connector>
<targetReference>Add_To_Update_Collection</targetReference>
</connector>
</assignments>
<description>Template demonstrating Loop element patterns with proper connector structure and bulkification.</description>
<label>Loop_Element_Patterns</label>
<processType>AutoLaunchedFlow</processType>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 1: BASIC LOOP (Ascending Order)
═══════════════════════════════════════════════════════════════════════════════
Standard loop pattern iterating first-to-last through collection.
Flow:
Loop_Through_Records
│
├─── nextValueConnector ──→ Process_Current_Record (inside loop)
│ │
│ ↓
│ Add_To_Update_Collection
│ │
│←───────────────────────────────────┘ (loops back)
│
└─── noMoreValuesConnector ──→ Bulk_Update_Records (after loop)
-->
<loops>
<name>Loop_Through_Records</name>
<label>Loop Through Records</label>
<locationX>0</locationX>
<locationY>0</locationY>
<!-- Collection to iterate over -->
<collectionReference>col_SourceRecords</collectionReference>
<!-- Ascending = first to last -->
<iterationOrder>Asc</iterationOrder>
<!-- INSIDE the loop body - first element to execute per iteration -->
<nextValueConnector>
<targetReference>Process_Current_Record</targetReference>
</nextValueConnector>
<!-- AFTER loop completes - when collection exhausted -->
<noMoreValuesConnector>
<targetReference>Bulk_Update_Records</targetReference>
</noMoreValuesConnector>
</loops>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 2: DESCENDING ORDER LOOP
═══════════════════════════════════════════════════════════════════════════════
Process collection from last to first. Useful when:
- Processing in reverse chronological order
- Removing items while iterating (avoid index shifting issues)
-->
<loops>
<name>Loop_Reverse_Order</name>
<label>Loop Reverse Order</label>
<locationX>0</locationX>
<locationY>0</locationY>
<collectionReference>col_SourceRecords</collectionReference>
<!-- Descending = last to first -->
<iterationOrder>Desc</iterationOrder>
<nextValueConnector>
<targetReference>Process_Current_Record</targetReference>
</nextValueConnector>
<noMoreValuesConnector>
<targetReference>Bulk_Update_Records</targetReference>
</noMoreValuesConnector>
</loops>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 3: LOOP WITH MANUAL VARIABLE ASSIGNMENT
═══════════════════════════════════════════════════════════════════════════════
Use assignNextValueToReference when you need:
- A specifically named loop variable
- Better readability in complex flows
- Consistency with existing variable naming conventions
NOTE: When using assignNextValueToReference, the loop element name
CANNOT be used as a reference - only the assigned variable works.
-->
<loops>
<name>Loop_With_Named_Variable</name>
<label>Loop With Named Variable</label>
<locationX>0</locationX>
<locationY>0</locationY>
<collectionReference>col_SourceRecords</collectionReference>
<iterationOrder>Asc</iterationOrder>
<!-- Explicitly assign current item to named variable -->
<assignNextValueToReference>rec_LoopItem</assignNextValueToReference>
<nextValueConnector>
<targetReference>Process_Current_Record</targetReference>
</nextValueConnector>
<noMoreValuesConnector>
<targetReference>Bulk_Update_Records</targetReference>
</noMoreValuesConnector>
</loops>
<!-- Get Records to populate collection -->
<recordLookups>
<name>Get_Records</name>
<label>Get Records</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignNullValuesIfNoRecordsFound>false</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Decision_Has_Records</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
<field>IsActive</field>
<operator>EqualTo</operator>
<value>
<booleanValue>true</booleanValue>
</value>
</filters>
<getFirstRecordOnly>false</getFirstRecordOnly>
<object>Account</object>
<outputReference>col_SourceRecords</outputReference>
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<queriedFields>Description</queriedFields>
</recordLookups>
<!-- Bulk Update AFTER loop (critical for performance) -->
<recordUpdates>
<name>Bulk_Update_Records</name>
<label>Bulk Update Records</label>
<locationX>0</locationX>
<locationY>0</locationY>
<faultConnector>
<targetReference>Handle_Error</targetReference>
</faultConnector>
<inputReference>col_RecordsToUpdate</inputReference>
</recordUpdates>
<!-- Error Handler -->
<assignments>
<name>Handle_Error</name>
<label>Handle Error</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>var_ErrorMessage</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>$Flow.FaultMessage</elementReference>
</value>
</assignmentItems>
</assignments>
<!-- Decision: Check if records exist before looping -->
<decisions>
<name>Decision_Has_Records</name>
<label>Has Records?</label>
<locationX>0</locationX>
<locationY>0</locationY>
<defaultConnectorLabel>No Records</defaultConnectorLabel>
<rules>
<name>Has_Records</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>col_SourceRecords</leftValueReference>
<operator>IsNull</operator>
<rightValue>
<booleanValue>false</booleanValue>
</rightValue>
</conditions>
<connector>
<targetReference>Loop_Through_Records</targetReference>
</connector>
<label>Has Records</label>
</rules>
</decisions>
<!-- Start Element -->
<start>
<locationX>0</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Initialize_Variables</targetReference>
</connector>
</start>
<status>Draft</status>
<!--
═══════════════════════════════════════════════════════════════════════════════
VARIABLES
═══════════════════════════════════════════════════════════════════════════════
-->
<!-- Source collection (populated by Get Records) -->
<variables>
<name>col_SourceRecords</name>
<dataType>SObject</dataType>
<isCollection>true</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Account</objectType>
</variables>
<!-- Collection for bulk update -->
<variables>
<name>col_RecordsToUpdate</name>
<dataType>SObject</dataType>
<isCollection>true</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Account</objectType>
</variables>
<!-- Current record being processed -->
<variables>
<name>rec_CurrentRecord</name>
<dataType>SObject</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Account</objectType>
</variables>
<!-- For Pattern 3: Named loop variable -->
<variables>
<name>rec_LoopItem</name>
<dataType>SObject</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Account</objectType>
</variables>
<!-- Counter -->
<variables>
<name>var_ProcessedCount</name>
<dataType>Number</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
<scale>0</scale>
</variables>
<!-- Error message -->
<variables>
<name>var_ErrorMessage</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
</variables>
<!--
═══════════════════════════════════════════════════════════════════════════════
BEST PRACTICES FOR LOOPS
═══════════════════════════════════════════════════════════════════════════════
1. ALWAYS CHECK FOR EMPTY COLLECTION
- Add Decision element before Loop
- Check: col_Records IsNull = false
- Prevents unnecessary processing
2. AVOID DML INSIDE LOOPS
- Collect records in col_RecordsToUpdate
- Single Update after noMoreValuesConnector
- Prevents governor limit exhaustion
3. USE MEANINGFUL VARIABLE NAMES
- col_ prefix for collections
- rec_ prefix for single records
- var_ prefix for primitives
4. CONSIDER ITERATION ORDER
- Asc: Standard processing
- Desc: When removing items or reverse chronological
5. HANDLE LARGE COLLECTIONS
- Flow handles batching automatically
- Consider Batch Apex for 50,000+ records
6. REFERENCE LOOP VARIABLE CORRECTLY
- Without assignNextValueToReference: Use loop element name
- With assignNextValueToReference: Use the assigned variable name
═══════════════════════════════════════════════════════════════════════════════
COMMON MISTAKES
═══════════════════════════════════════════════════════════════════════════════
❌ MISTAKE 1: DML inside loop
Loop → Update Record → Loop (BAD!)
✅ CORRECT:
Loop → Add to Collection → Loop → (after loop) → Bulk Update
❌ MISTAKE 2: No null check before loop
Get Records → Loop (may fail on null)
✅ CORRECT:
Get Records → Decision (IsNull check) → Loop
❌ MISTAKE 3: Wrong connector back to loop
Loop → Process → (missing connector back)
✅ CORRECT:
Last element inside loop MUST connect back to Loop element
═══════════════════════════════════════════════════════════════════════════════
-->
</Flow>
<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<!--
Template: Record Delete (recordDeletes) Patterns v1.0.0
API Version: 66.0+
═══════════════════════════════════════════════════════════════════════════════
RECORDDELETES ELEMENT OPTIONS
═══════════════════════════════════════════════════════════════════════════════
┌──────────────────────────────────┬─────────────────────────────────────────┐
│ Element │ Purpose │
├──────────────────────────────────┼─────────────────────────────────────────┤
│ object │ API name of SObject (filter-based) │
│ filters │ WHERE clause for filter-based delete │
│ filterLogic │ "and" / "or" / custom logic │
│ inputReference │ Variable reference for direct delete │
│ connector │ Next element on success │
│ faultConnector │ Next element on failure │
└──────────────────────────────────┴─────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════════════════════
TWO DELETE APPROACHES
═══════════════════════════════════════════════════════════════════════════════
1. FILTER-BASED DELETE
- Specify object + filters
- Flow queries and deletes matching records
- Use for: Deleting records by criteria
2. REFERENCE-BASED DELETE
- Specify inputReference (record or collection variable)
- Deletes the referenced record(s)
- Use for: Deleting records you've already queried
═══════════════════════════════════════════════════════════════════════════════
⚠️ IMPORTANT: DELETE IS PERMANENT (unless Recycle Bin)
═══════════════════════════════════════════════════════════════════════════════
- Records go to Recycle Bin (recoverable for 15 days)
- Hard delete requires Apex with Database.emptyRecycleBin()
- Always add confirmation in Screen Flows before delete
- Consider soft delete (Status field) instead for audit trails
═══════════════════════════════════════════════════════════════════════════════
VARIABLE NAMING (v2.0.0):
- var_ for regular variables (e.g., var_DeleteCount)
- col_ for collections (e.g., col_RecordsToDelete)
- rec_ for record variables (e.g., rec_RecordToDelete)
- inp_ for input variables (e.g., inp_RecordId)
- out_ for output variables (e.g., out_WasDeleted)
═══════════════════════════════════════════════════════════════════════════════
-->
<apiVersion>66.0</apiVersion>
<!--
═══════════════════════════════════════════════════════════════════════════════
ASSIGNMENTS (Alphabetical order required)
═══════════════════════════════════════════════════════════════════════════════
-->
<assignments>
<name>Handle_Delete_Error</name>
<label>Handle Delete Error</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>var_ErrorMessage</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>$Flow.FaultMessage</elementReference>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>out_WasDeleted</assignToReference>
<operator>Assign</operator>
<value>
<booleanValue>false</booleanValue>
</value>
</assignmentItems>
</assignments>
<assignments>
<name>Set_Delete_Success</name>
<label>Set Delete Success</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignmentItems>
<assignToReference>out_WasDeleted</assignToReference>
<operator>Assign</operator>
<value>
<booleanValue>true</booleanValue>
</value>
</assignmentItems>
</assignments>
<description>Template demonstrating recordDeletes patterns: filter-based, reference-based, and bulk delete with fault handling.</description>
<label>Record_Delete_Patterns</label>
<processType>AutoLaunchedFlow</processType>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 1: DELETE SINGLE RECORD BY REFERENCE
═══════════════════════════════════════════════════════════════════════════════
Delete a specific record you've already queried.
Use after Get Records when you have the record variable.
-->
<recordDeletes>
<name>Delete_Single_Record</name>
<label>Delete Single Record</label>
<locationX>0</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Set_Delete_Success</targetReference>
</connector>
<faultConnector>
<targetReference>Handle_Delete_Error</targetReference>
</faultConnector>
<!-- Reference to a single record variable -->
<inputReference>rec_RecordToDelete</inputReference>
</recordDeletes>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 2: DELETE COLLECTION (BULK DELETE)
═══════════════════════════════════════════════════════════════════════════════
Delete multiple records at once using a collection variable.
Most efficient for bulk operations - single DML statement.
-->
<recordDeletes>
<name>Delete_Record_Collection</name>
<label>Delete Record Collection</label>
<locationX>0</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Set_Delete_Success</targetReference>
</connector>
<faultConnector>
<targetReference>Handle_Delete_Error</targetReference>
</faultConnector>
<!-- Reference to collection variable -->
<inputReference>col_RecordsToDelete</inputReference>
</recordDeletes>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 3: DELETE BY FILTER (Direct Query + Delete)
═══════════════════════════════════════════════════════════════════════════════
Delete records matching filter criteria without pre-querying.
Flow internally queries and deletes in one step.
Equivalent to: DELETE [SELECT Id FROM Task WHERE Status = 'Completed' AND WhatId = :inp_AccountId]
-->
<recordDeletes>
<name>Delete_By_Filter</name>
<label>Delete Completed Tasks</label>
<locationX>0</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Set_Delete_Success</targetReference>
</connector>
<faultConnector>
<targetReference>Handle_Delete_Error</targetReference>
</faultConnector>
<filterLogic>and</filterLogic>
<filters>
<field>Status</field>
<operator>EqualTo</operator>
<value>
<stringValue>Completed</stringValue>
</value>
</filters>
<filters>
<field>WhatId</field>
<operator>EqualTo</operator>
<value>
<elementReference>inp_AccountId</elementReference>
</value>
</filters>
<!-- Object specified for filter-based delete -->
<object>Task</object>
</recordDeletes>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 4: DELETE WITH COMPLEX FILTER LOGIC
═══════════════════════════════════════════════════════════════════════════════
Delete using OR conditions or custom filter logic.
Example: Delete tasks that are either Completed OR Cancelled
-->
<recordDeletes>
<name>Delete_With_Or_Logic</name>
<label>Delete Closed Tasks</label>
<locationX>0</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Set_Delete_Success</targetReference>
</connector>
<faultConnector>
<targetReference>Handle_Delete_Error</targetReference>
</faultConnector>
<!-- Custom filter logic with OR -->
<filterLogic>(1 OR 2) AND 3</filterLogic>
<filters>
<!-- Filter 1 -->
<field>Status</field>
<operator>EqualTo</operator>
<value>
<stringValue>Completed</stringValue>
</value>
</filters>
<filters>
<!-- Filter 2 -->
<field>Status</field>
<operator>EqualTo</operator>
<value>
<stringValue>Cancelled</stringValue>
</value>
</filters>
<filters>
<!-- Filter 3: Scoped to specific account -->
<field>WhatId</field>
<operator>EqualTo</operator>
<value>
<elementReference>inp_AccountId</elementReference>
</value>
</filters>
<object>Task</object>
</recordDeletes>
<!--
═══════════════════════════════════════════════════════════════════════════════
PATTERN 5: SAFE DELETE WITH PRE-QUERY
═══════════════════════════════════════════════════════════════════════════════
Recommended pattern: Query first, then delete.
Allows validation/logging before delete.
-->
<recordLookups>
<name>Get_Records_To_Delete</name>
<label>Get Records To Delete</label>
<locationX>0</locationX>
<locationY>0</locationY>
<assignNullValuesIfNoRecordsFound>false</assignNullValuesIfNoRecordsFound>
<connector>
<targetReference>Decision_Has_Records</targetReference>
</connector>
<filterLogic>and</filterLogic>
<filters>
<field>AccountId</field>
<operator>EqualTo</operator>
<value>
<elementReference>inp_AccountId</elementReference>
</value>
</filters>
<filters>
<field>IsActive</field>
<operator>EqualTo</operator>
<value>
<booleanValue>false</booleanValue>
</value>
</filters>
<getFirstRecordOnly>false</getFirstRecordOnly>
<object>Contact</object>
<outputReference>col_InactiveContacts</outputReference>
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
</recordLookups>
<decisions>
<name>Decision_Has_Records</name>
<label>Has Records to Delete?</label>
<locationX>0</locationX>
<locationY>0</locationY>
<defaultConnectorLabel>No Records</defaultConnectorLabel>
<rules>
<name>Has_Records</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>col_InactiveContacts</leftValueReference>
<operator>IsNull</operator>
<rightValue>
<booleanValue>false</booleanValue>
</rightValue>
</conditions>
<connector>
<targetReference>Delete_Inactive_Contacts</targetReference>
</connector>
<label>Has Records</label>
</rules>
</decisions>
<recordDeletes>
<name>Delete_Inactive_Contacts</name>
<label>Delete Inactive Contacts</label>
<locationX>0</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Set_Delete_Success</targetReference>
</connector>
<faultConnector>
<targetReference>Handle_Delete_Error</targetReference>
</faultConnector>
<inputReference>col_InactiveContacts</inputReference>
</recordDeletes>
<!-- Start Element -->
<start>
<locationX>0</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Get_Records_To_Delete</targetReference>
</connector>
</start>
<status>Draft</status>
<!--
═══════════════════════════════════════════════════════════════════════════════
VARIABLES
═══════════════════════════════════════════════════════════════════════════════
-->
<!-- Input Variables -->
<variables>
<name>inp_AccountId</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<variables>
<name>inp_RecordId</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<!-- Output Variables -->
<variables>
<name>out_WasDeleted</name>
<dataType>Boolean</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<!-- Record Variables -->
<variables>
<name>rec_RecordToDelete</name>
<dataType>SObject</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Account</objectType>
</variables>
<!-- Collection Variables -->
<variables>
<name>col_RecordsToDelete</name>
<dataType>SObject</dataType>
<isCollection>true</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Account</objectType>
</variables>
<variables>
<name>col_InactiveContacts</name>
<dataType>SObject</dataType>
<isCollection>true</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<objectType>Contact</objectType>
</variables>
<!-- Error Handling -->
<variables>
<name>var_ErrorMessage</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<!--
═══════════════════════════════════════════════════════════════════════════════
BEST PRACTICES FOR RECORDDELETES
═══════════════════════════════════════════════════════════════════════════════
1. ALWAYS ADD FAULT CONNECTOR
- Delete operations can fail (locked records, triggers, etc.)
- Capture $Flow.FaultMessage for debugging
- Decide: Continue or abort flow?
2. PREFER REFERENCE-BASED DELETE
- Query first → Validate → Delete
- More control and visibility
- Easier debugging and logging
3. USE COLLECTION FOR BULK DELETE
- Single DML statement for multiple records
- More efficient than individual deletes
- Respects governor limits better
4. CONFIRM BEFORE DELETE IN SCREEN FLOWS
- Add confirmation screen before delete
- Show record name/details being deleted
- Prevent accidental data loss
5. CONSIDER SOFT DELETE ALTERNATIVE
- Add IsDeleted__c or Status field
- Set to 'Inactive' instead of delete
- Maintains audit trail
═══════════════════════════════════════════════════════════════════════════════
COMMON ERRORS AND SOLUTIONS
═══════════════════════════════════════════════════════════════════════════════
┌─────────────────────────────────┬────────────────────────────────────────────┐
│ Error │ Solution │
├─────────────────────────────────┼────────────────────────────────────────────┤
│ ENTITY_IS_LOCKED │ Record locked by approval/other process │
│ │ → Wait and retry, or skip locked records │
├─────────────────────────────────┼────────────────────────────────────────────┤
│ DELETE_REQUIRED_ON_CASCADE │ Child records exist (Master-Detail) │
│ │ → Delete children first │
├─────────────────────────────────┼────────────────────────────────────────────┤
│ INSUFFICIENT_ACCESS │ User lacks delete permission │
│ │ → Check profile/permission set │
├─────────────────────────────────┼────────────────────────────────────────────┤
│ DELETE_FAILED │ Trigger/validation blocked delete │
│ │ → Check object triggers and validations │
└─────────────────────────────────┴────────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════════════════════
WHEN TO USE EACH PATTERN
═══════════════════════════════════════════════════════════════════════════════
REFERENCE-BASED (inputReference):
✓ You've already queried the record(s)
✓ You need to validate before delete
✓ You want to log which records were deleted
✓ You need the record data for post-delete processing
FILTER-BASED (object + filters):
✓ Simple criteria-based cleanup
✓ You don't need the record data
✓ One-time bulk cleanup operations
✗ No visibility into what's being deleted
═══════════════════════════════════════════════════════════════════════════════
-->
</Flow>
Flow Documentation: {{FLOW_NAME}}
Status: {{STATUS}} API Version: {{API_VERSION}} Created: {{CREATED_DATE}} Last Modified: {{MODIFIED_DATE}} Owner: {{OWNER}}
---
Overview
Purpose: {{PURPOSE}}
Flow Type: {{FLOW_TYPE}}
Business Context: {{BUSINESS_CONTEXT}}
---
Entry/Exit Criteria
Entry Criteria
{{ENTRY_CRITERIA}}
Exit Criteria
{{EXIT_CRITERIA}}
---
Logic Design
Decision Points
{{DECISION_POINTS}}
Branching Complexity
Complexity Level: {{COMPLEXITY_LEVEL}}
Operations Summary
- SOQL Queries: {{SOQL_COUNT}}
- DML Operations: {{DML_COUNT}}
- Subflow Calls: {{SUBFLOW_COUNT}}
- Apex Actions: {{APEX_ACTION_COUNT}}
---
Orchestration & Architecture
Architecture Pattern
Pattern Used: {{ORCHESTRATION_PATTERN}}
Parent Flow
{{PARENT_FLOW}}
Child Subflows Called
{{CHILD_SUBFLOWS}}
Coordination Pattern
{{COORDINATION_PATTERN}}
---
Performance & Bulk Safety
Bulk Testing
- Tested with 200+ records: {{BULK_TESTED}}
- Transform Element Used: {{TRANSFORM_USED}}
- Bulkification Status: {{BULKIFICATION_STATUS}}
Governor Limit Estimates
- DML Rows: {{DML_ROWS_ESTIMATE}} / 10,000
- SOQL Queries: {{SOQL_QUERIES_ESTIMATE}} / 100
- DML Statements: {{DML_STATEMENTS_ESTIMATE}} / 150
- CPU Time: {{CPU_TIME_ESTIMATE}} / 10,000ms
Simulation Results
{{SIMULATION_RESULTS}}
---
Error Handling & Observability
Fault Paths
Coverage: {{FAULT_PATH_COVERAGE}}
Error Logging
Method: {{ERROR_LOGGING_METHOD}}
Error Capture:
- Flow Name: {{ERROR_CAPTURE_FLOW_NAME}}
- Record ID: {{ERROR_CAPTURE_RECORD_ID}}
- Error Message: {{ERROR_CAPTURE_MESSAGE}}
- Timestamp: {{ERROR_CAPTURE_TIMESTAMP}}
Alert Mechanism
{{ALERT_MECHANISM}}
---
Reusability & Components
Subflows Used
{{SUBFLOWS_USED_LIST}}
Can Be Reused
Reusable: {{IS_REUSABLE}}
Invocable from Apex: {{INVOCABLE_FROM_APEX}}
Input Variables
{{INPUT_VARIABLES}}
Output Variables
{{OUTPUT_VARIABLES}}
---
Security & Governance
Running Mode
Mode: {{RUNNING_MODE}}
Bypasses Permissions: {{BYPASSES_PERMISSIONS}}
{{RUNNING_MODE_JUSTIFICATION}}
Data Access
Objects Accessed: {{OBJECTS_ACCESSED}}
Sensitive Fields: {{SENSITIVE_FIELDS}}
Compliance
{{COMPLIANCE_REQUIREMENTS}}
Testing Coverage
- Standard User Profile: {{TESTED_STANDARD_USER}}
- Custom Profiles: {{TESTED_CUSTOM_PROFILES}}
- Permission Sets: {{TESTED_PERMISSION_SETS}}
- FLS Respected: {{FLS_RESPECTED}}
- CRUD Respected: {{CRUD_RESPECTED}}
Architecture Review
- Reviewed By: {{REVIEWED_BY}}
- Review Date: {{REVIEW_DATE}}
- Status: {{REVIEW_STATUS}}
---
Testing Status
Unit Testing
- All Paths Tested: {{UNIT_TESTING_PATHS}}
- Error Scenarios: {{UNIT_TESTING_ERRORS}}
- Edge Cases: {{UNIT_TESTING_EDGE_CASES}}
Bulk Testing
- 200+ Records: {{BULK_TESTING_RECORDS}}
- No Governor Limits: {{BULK_TESTING_LIMITS}}
- Performance: {{BULK_TESTING_PERFORMANCE}}
Integration Testing
- Related Flows: {{INTEGRATION_RELATED_FLOWS}}
- External Systems: {{INTEGRATION_EXTERNAL}}
- UAT Completed: {{UAT_COMPLETED}}
Production Deployment
- Deployed: {{DEPLOYED}}
- Deployment Date: {{DEPLOYMENT_DATE}}
- Activated: {{ACTIVATED}}
---
Dependencies
Required Metadata
{{REQUIRED_METADATA}}
Required Objects
{{REQUIRED_OBJECTS}}
Required Fields
{{REQUIRED_FIELDS}}
Required Subflows
{{REQUIRED_SUBFLOWS}}
Required Apex Classes
{{REQUIRED_APEX}}
---
Change Log
| Date | Version | Change Description | Modified By |
|---|---|---|---|
| {{CHANGE_LOG_ENTRIES}} |
---
Troubleshooting
Common Issues
{{COMMON_ISSUES}}
Debug Steps
{{DEBUG_STEPS}}
Support Contacts
- Primary: {{SUPPORT_PRIMARY}}
- Backup: {{SUPPORT_BACKUP}}
- Team: {{SUPPORT_TEAM}}
---
Related Documentation
{{RELATED_DOCS}}
---
Notes
{{ADDITIONAL_NOTES}}
---
Generated automatically by SF-Flow Builder Documentation Generator Generation Date: {{GENERATION_DATE}}
<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<label>{{FLOW_LABEL}}</label>
<description>{{FLOW_DESCRIPTION}}</description>
<processType>AutoLaunchedFlow</processType>
<status>Draft</status>
<!-- Platform Event Trigger Configuration -->
<start>
<locationX>50</locationX>
<locationY>50</locationY>
<object>{{PLATFORM_EVENT_NAME}}__e</object>
<triggerType>PlatformEvent</triggerType>
<bulkSupport>true</bulkSupport>
<connector>
<targetReference>Decision_Check_Event_Criteria</targetReference>
</connector>
</start>
<!-- Variables for Event Data -->
<variables>
<name>varEventData</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
</variables>
<!-- Decision: Check Event Criteria -->
<decisions>
<name>Decision_Check_Event_Criteria</name>
<label>Check Event Criteria</label>
<locationX>50</locationX>
<locationY>150</locationY>
<defaultConnectorLabel>Default Outcome</defaultConnectorLabel>
<rules>
<name>Criteria_Met</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>$Record.{{EVENT_FIELD}}__c</leftValueReference>
<operator>EqualTo</operator>
<rightValue>
<stringValue>{{EXPECTED_VALUE}}</stringValue>
</rightValue>
</conditions>
<connector>
<targetReference>Process_Event</targetReference>
</connector>
<label>Criteria Met</label>
</rules>
</decisions>
<!-- Assignment: Process Event Data -->
<assignments>
<name>Process_Event</name>
<label>Process Event</label>
<locationX>50</locationX>
<locationY>250</locationY>
<assignmentItems>
<assignToReference>varEventData</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>$Record.{{EVENT_FIELD}}__c</elementReference>
</value>
</assignmentItems>
<connector>
<targetReference>Create_Response_Record</targetReference>
</connector>
</assignments>
<!-- Create Record Based on Event -->
<recordCreates>
<name>Create_Response_Record</name>
<label>Create Response Record</label>
<locationX>50</locationX>
<locationY>350</locationY>
<faultConnector>
<targetReference>Handle_Error</targetReference>
</faultConnector>
<inputAssignments>
<field>{{TARGET_FIELD}}</field>
<value>
<elementReference>varEventData</elementReference>
</value>
</inputAssignments>
<object>{{TARGET_OBJECT}}</object>
</recordCreates>
<!-- Error Handler -->
<assignments>
<name>Handle_Error</name>
<label>Handle Error</label>
<locationX>200</locationX>
<locationY>350</locationY>
<assignmentItems>
<assignToReference>varErrorMessage</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>$Flow.FaultMessage</elementReference>
</value>
</assignmentItems>
</assignments>
<!-- Error Message Variable -->
<variables>
<name>varErrorMessage</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
</variables>
</Flow>
<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<description>Reusable email alert subflow for sending notifications. Accepts email addresses, subject, and body as input. Use for standardized email notifications across flows.</description>
<actionCalls>
<name>Send_Email</name>
<label>Send Email</label>
<locationX>0</locationX>
<locationY>0</locationY>
<actionName>emailSimple</actionName>
<actionType>emailSimple</actionType>
<inputParameters>
<name>emailAddresses</name>
<value>
<elementReference>varEmailAddresses</elementReference>
</value>
</inputParameters>
<inputParameters>
<name>emailBody</name>
<value>
<elementReference>varEmailBody</elementReference>
</value>
</inputParameters>
<inputParameters>
<name>emailSubject</name>
<value>
<elementReference>varEmailSubject</elementReference>
</value>
</inputParameters>
</actionCalls>
<label>Sub_SendEmailAlert</label>
<processType>AutoLaunchedFlow</processType>
<start>
<locationX>0</locationX>
<locationY>0</locationY>
<connector>
<targetReference>Send_Email</targetReference>
</connector>
</start>
<status>Draft</status>
<variables>
<name>varEmailAddresses</name>
<dataType>String</dataType>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<variables>
<name>varEmailBody</name>
<dataType>String</dataType>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<variables>
<name>varEmailSubject</name>
<dataType>String</dataType>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
</Flow>