
Sf Flow
- 1.4k installs
- 423 repo stars
- Updated April 27, 2026
- jaganpro/sf-skills
sf-flow is an agent skill that apply sf-flow agent skill workflows from documented skill.md guidance.
About
sf-flow is an agent skill from jaganpro/sf-skills that apply sf-flow agent skill workflows from documented skill.md guidance. # 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: - `. Developers invoke sf-flow during build/integrations work for automation & workflows tasks. The skill documents triggers, prerequisites, and step-by-step workflows grounded in SKILL.md. Compatible with Claude Code, Cursor, and Codex agent runtimes that load marketplace skills. Review the Security Audits panel on this listing before installing in production environments. Category Automation & Workflows with development vertical focus supports repeatable agent-guided delivery.
- sf-flow: Salesforce Flow Creation and Validation
- When This Skill Owns the Task
- Use `sf-flow` when the work involves:
- Flow Builder architecture and XML generation
- record-triggered, screen, scheduled, autolaunched, or platform-event flows
Sf Flow by the numbers
- 1,428 all-time installs (skills.sh)
- +4 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #221 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
sf-flow capabilities & compatibility
- Capabilities
- sf flow: salesforce flow creation and validation · when this skill owns the task · use `sf flow` when the work involves: · flow builder architecture and xml generation · record triggered, screen, scheduled, autolaunche
- Use cases
- orchestration
What sf-flow says it does
- Flow Builder architecture and XML generation
- record-triggered, screen, scheduled, autolaunched, or platform-event flows
- Flow-specific bulk safety, fault paths, and subflow orchestration
npx skills add https://github.com/jaganpro/sf-skills --skill sf-flowAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.4k |
|---|---|
| repo stars | ★ 423 |
| Security audit | 3 / 3 scanners passed |
| Last updated | April 27, 2026 |
| Repository | jaganpro/sf-skills ↗ |
What it does
Apply sf-flow agent skill workflows from documented SKILL.md guidance.
Who is it for?
Developers working on automation & workflows during build tasks.
Skip if: Tasks outside Automation & Workflows scope described in SKILL.md.
When should I use this skill?
Apply sf-flow agent skill workflows from documented SKILL.md guidance.
What you get
Completed automation & workflows workflow aligned with SKILL.md steps.
- Flow XML templates
- LlmPrompt decision configurations
- AI outcome instruction blocks
By the numbers
- Requires Salesforce API version 62.0 or newer
- Consumes Einstein AI credits per AI decision evaluation
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>
Related skills
Forks & variants (1)
Sf Flow has 1 known copy in the catalog totaling 33 installs. They canonicalize to this original listing.
- jaganpro - 33 installs
How it compares
Choose sf-flow over generic CRM skills when authoring Salesforce Flow XML with Einstein LlmPrompt decision nodes, not Apex-only logic.
FAQ
What does sf-flow do?
Apply sf-flow agent skill workflows from documented SKILL.md guidance.
When should I use sf-flow?
During build integrations work for automation & workflows.
Is sf-flow safe to install?
Review the Security Audits panel on this listing before production use.