Salesforce Flow XML Metadata Gotchas
Critical XML metadata constraints and known issues when deploying flows via Metadata API.
storeOutputAutomatically Data Leak Risk (v2.0.0)
⚠️ SECURITY WARNING: When storeOutputAutomatically="true" in recordLookups, ALL fields are retrieved and stored.
Risks
- Data Leak: Sensitive fields (SSN, salary, etc.) may be exposed unintentionally
- Performance: Large objects with many fields impact query performance
- Screen Flow Exposure: In screen flows, external users could access all data
Recommended Pattern
Always specify only the fields you need:
<recordLookups>
<name>Get_Account</name>
<!-- Specify exact fields needed -->
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<queriedFields>Industry</queriedFields>
<!-- Store in explicit variable -->
<outputReference>rec_Account</outputReference>
</recordLookups>
Avoid:
<recordLookups>
<name>Get_Account</name>
<!-- Retrieves ALL fields - security risk! -->
<storeOutputAutomatically>true</storeOutputAutomatically>
</recordLookups>
Relationship Fields Not Supported in recordLookups (CRITICAL)
⚠️ DEPLOYMENT BLOCKER: Flow's Get Records (recordLookups) CANNOT query parent relationship fields.
What Doesn't Work
<!-- ❌ THIS WILL FAIL DEPLOYMENT -->
<recordLookups>
<name>Get_User</name>
<object>User</object>
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<queriedFields>Manager.Name</queriedFields> <!-- FAILS! -->
</recordLookups>
Error: field integrity exception: unknown (The field "Manager.Name" for the object "User" doesn't exist.)
Why It Fails
- Flow's recordLookups only supports direct fields on the queried object
- Parent relationship traversal (dot notation like
Parent.Field) is NOT supported - This is different from SOQL in Apex which supports relationship queries
Fields That WON'T Work
| Object | Invalid Field | Error |
|---|---|---|
| User | Manager.Name |
Manager.Name doesn't exist |
| Contact | Account.Name |
Account.Name doesn't exist |
| Case | Account.Owner.Email |
Account.Owner.Email doesn't exist |
| Opportunity | Account.Industry |
Account.Industry doesn't exist |
Correct Solution: Two-Step Query
<!-- Step 1: Get the child record with lookup ID -->
<recordLookups>
<name>Get_User</name>
<object>User</object>
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<queriedFields>ManagerId</queriedFields> <!-- ✅ Get the ID only -->
<outputReference>rec_User</outputReference>
</recordLookups>
<!-- Step 2: Query parent record using the lookup ID -->
<recordLookups>
<name>Get_Manager</name>
<object>User</object>
<filters>
<field>Id</field>
<operator>EqualTo</operator>
<value>
<elementReference>rec_User.ManagerId</elementReference>
</value>
</filters>
<queriedFields>Id</queriedFields>
<queriedFields>Name</queriedFields>
<outputReference>rec_Manager</outputReference>
</recordLookups>
Flow Routing
Ensure your flow checks for null before using the parent record:
<decisions>
<name>Check_Manager_Exists</name>
<rules>
<conditions>
<leftValueReference>rec_Manager</leftValueReference>
<operator>IsNull</operator>
<rightValue><booleanValue>false</booleanValue></rightValue>
</conditions>
</rules>
</decisions>
$Record vs $Record__c Confusion (Record-Triggered Flows)
⚠️ COMMON MISTAKE: Confusing Flow's $Record with Process Builder's $Record__c.
What's the Difference?
| Variable | Context | Usage |
|---|---|---|
$Record |
Flow (Record-Triggered) | Single record that triggered the flow |
$Record__c |
Process Builder | Collection of records in trigger batch |
The Mistake
Trying to create a loop over $Record__c in a Flow:
<!-- ❌ THIS DOES NOT EXIST IN FLOWS -->
<loops>
<collectionReference>$Record__c</collectionReference> <!-- INVALID! -->
</loops>
Why This Happens
- Process Builder used
$Record__cto represent the batch of triggering records - Developers migrating from Process Builder assume Flows work the same way
- In Flows,
$Recordis always a single record, not a collection - The platform handles bulk batching automatically
Correct Approach in Record-Triggered Flows
Use $Record directly without loops:
<!-- ✅ CORRECT: Direct access to triggered record -->
<decisions>
<conditions>
<leftValueReference>$Record.StageName</leftValueReference>
<operator>EqualTo</operator>
<rightValue><stringValue>Closed Won</stringValue></rightValue>
</conditions>
</decisions>
<!-- ✅ Build task using $Record fields -->
<assignments>
<assignmentItems>
<assignToReference>rec_Task.WhatId</assignToReference>
<value><elementReference>$Record.Id</elementReference></value>
</assignmentItems>
</assignments>
When You DO Need Loops
Only when processing related records, not the triggered record:
<!-- ✅ CORRECT: Loop over RELATED records -->
<recordLookups>
<filters>
<field>AccountId</field>
<value><elementReference>$Record.AccountId</elementReference></value>
</filters>
<outputReference>col_RelatedContacts</outputReference>
</recordLookups>
<loops>
<collectionReference>col_RelatedContacts</collectionReference> <!-- ✅ Valid -->
</loops>
recordLookups Conflicts
NEVER use both <storeOutputAutomatically> AND <outputReference> together.
Choose ONE approach:
<!-- Option 1: Auto-store (creates variable automatically) - NOT RECOMMENDED -->
<storeOutputAutomatically>true</storeOutputAutomatically>
<!-- Option 2: Explicit variable - RECOMMENDED -->
<outputReference>rec_AccountRecord</outputReference>
Element Ordering in recordLookups
Elements must follow this order:
<name>2.<label>3.<locationX>4.<locationY>5.<assignNullValuesIfNoRecordsFound>6.<connector>7.<filterLogic>8.<filters>9.<getFirstRecordOnly>10.<object>11.<outputReference>OR<storeOutputAutomatically>12.<queriedFields>
Transform Element
Recommendation: Create Transform elements in Flow Builder UI, then deploy - do NOT hand-write.
Issues with hand-written Transform:
- Complex nested XML structure with strict ordering
inputReferenceplacement varies by context- Multiple conflicting rules in Metadata API
Subflow Calling Limitation (Metadata API Constraint)
Record-triggered flows (processType="AutoLaunchedFlow") CANNOT call subflows via XML deployment.
Root Cause: Salesforce Metadata API does not support the "flow" action type for AutoLaunchedFlow process types.
Valid action types for AutoLaunchedFlow: apex, chatterPost, emailAlert, emailSimple, and platform-specific actions - but NOT "flow".
Error message: "You can't use the Flows action type in flows with the Autolaunched Flow process type"
Screen Flows (processType="Flow") CAN call subflows successfully via XML deployment.
UI vs XML: Flow Builder UI may use different internal mechanisms - UI capabilities may differ from direct XML deployment.
Recommended Solution for Record-Triggered Flows
Use inline orchestration instead of subflows:
- Organize logic into clear sections with descriptive element naming
- Pattern:
Decision_CheckCriteria→Assignment_SetFields→Create_Record - Add XML comments to delineate sections:
<!-- Section 1: Contact Creation -->
Benefits: Single atomic flow, no deployment dependencies, full execution control.
Reference: Salesforce Help Article 000396957
Fault Connectors Cannot Self-Reference (CRITICAL)
⚠️ DEPLOYMENT BLOCKER: An element CANNOT have a fault connector pointing to itself.
What Doesn't Work
<!-- ❌ THIS WILL FAIL DEPLOYMENT -->
<recordUpdates>
<name>Update_Account</name>
<label>Update Account</label>
<connector>
<targetReference>Next_Element</targetReference>
</connector>
<faultConnector>
<targetReference>Update_Account</targetReference> <!-- FAILS! Self-reference -->
</faultConnector>
<!-- ... -->
</recordUpdates>
Error: The element "Update_Account" cannot be connected to itself.
Why This Matters
- Self-referencing fault connectors would create infinite loops on failure
- Salesforce validates this during deployment and blocks it
- This applies to ALL connector types (faultConnector, connector, nextValueConnector, etc.)
Correct Fault Handling Patterns
Option 1: Route to dedicated error handler:
<recordUpdates>
<name>Update_Account</name>
<faultConnector>
<targetReference>Handle_Update_Error</targetReference> <!-- ✅ Different element -->
</faultConnector>
</recordUpdates>
<assignments>
<name>Handle_Update_Error</name>
<!-- Log error, set flag, etc. -->
</assignments>
Option 2: Route to error logging assignment:
<recordUpdates>
<name>Update_Account</name>
<faultConnector>
<targetReference>Log_Error_And_Continue</targetReference> <!-- ✅ -->
</faultConnector>
</recordUpdates>
Option 3: Omit fault connector (flow will terminate on error):
<recordUpdates>
<name>Update_Account</name>
<connector>
<targetReference>Next_Element</targetReference>
</connector>
<!-- No faultConnector - flow terminates on failure -->
</recordUpdates>
Best Practice
- Always route fault connectors to a dedicated error handling element
- Use an assignment to capture
{!$Flow.FaultMessage}before logging - Consider whether the flow should continue or terminate on failure
Root-Level Element Ordering (CRITICAL)
Salesforce Metadata API requires strict alphabetical ordering at root level.
Complete Element Type List (Alphabetical Order)
All elements of the same type MUST be grouped together. You CANNOT have elements of one type scattered across the file.
1. <apiVersion>
2. <assignments> ← ALL assignments together
3. <constants>
4. <decisions> ← ALL decisions together
5. <description>
6. <environments>
7. <formulas>
8. <interviewLabel>
9. <label>
10. <loops> ← ALL loops together
11. <processMetadataValues>
12. <processType>
13. <recordCreates> ← ALL recordCreates together
14. <recordDeletes> ← ALL recordDeletes together
15. <recordLookups> ← ALL recordLookups together
16. <recordUpdates> ← ALL recordUpdates together
17. <runInMode>
18. <screens> ← ALL screens together
19. <start>
20. <status>
21. <subflows> ← ALL subflows together
22. <textTemplates>
23. <variables> ← ALL variables together
24. <waits>
The Grouping Rule (CRITICAL)
Wrong - Assignment after loops section:
<assignments>
<name>Set_Initial_Values</name>
<!-- ... -->
</assignments>
<loops>
<name>Loop_Contacts</name>
<!-- ... -->
</loops>
<assignments> <!-- ❌ FAILS: Can't have assignments after loops! -->
<name>Increment_Counter</name>
<!-- ... -->
</assignments>
Error: Element assignments is duplicated at this location in type Flow
Correct - All assignments together:
<assignments> <!-- ✅ All assignments grouped -->
<name>Increment_Counter</name>
<!-- ... -->
</assignments>
<assignments>
<name>Set_Initial_Values</name>
<!-- ... -->
</assignments>
<loops>
<name>Loop_Contacts</name>
<!-- ... -->
</loops>
Why This Happens
When generating flows programmatically or manually editing XML:
- Easy to add a new element near related logic
- Salesforce requires ALL elements of same type to appear consecutively
- The "duplicate" error is misleading - it means elements aren't grouped
Note: API 60.0+ does NOT use <bulkSupport> - bulk processing is automatic.
Common Deployment Errors
| Error | Cause | Solution |
|---|---|---|
| "Element X is duplicated" | Elements not alphabetically ordered | Reorder elements |
| "Element bulkSupport invalid" | Using deprecated element (API 60.0+) | Remove <bulkSupport> |
| "Error parsing file" | Malformed XML | Validate XML syntax |
| "field 'X.Y' doesn't exist" | Relationship field in queriedFields | Use two-step query pattern |
| "$Record__Prior can only be used..." | Using $Record__Prior with Create trigger | Change to Update or CreateAndUpdate |
| "You can't use the Flows action type..." | Subflow in AutoLaunchedFlow | Use inline logic instead |
| "nothing is connected to the Start element" | Empty flow with no elements | Add at least one assignment connected to start |
Start Element Must Have Connector (For Agentforce Flow Actions)
⚠️ DEPLOYMENT BLOCKER: Flows used as Agent Script actions MUST have at least one element connected to the Start element.
What Doesn't Work
<!-- ❌ THIS WILL FAIL DEPLOYMENT -->
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>65.0</apiVersion>
<label>My Agent Flow</label>
<processType>AutoLaunchedFlow</processType>
<status>Active</status>
<variables>
<name>inp_SearchQuery</name>
<dataType>String</dataType>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<start>
<locationX>50</locationX>
<locationY>50</locationY>
<!-- No connector! -->
</start>
</Flow>
Error: field integrity exception: unknown (The flow can't run because nothing is connected to the Start element.)
Why This Happens
- Minimal flows with only inputs/outputs and no logic still need at least one element
- Salesforce validates that the flow has something to execute
- This is especially common when creating stub/mock flows for testing
Correct Pattern: Add Assignment Element
Even for simple pass-through flows, add at least one assignment:
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>65.0</apiVersion>
<label>My Agent Flow</label>
<processType>AutoLaunchedFlow</processType>
<status>Active</status>
<variables>
<name>inp_SearchQuery</name>
<dataType>String</dataType>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
<variables>
<name>out_Result</name>
<dataType>String</dataType>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<!-- ✅ Start connected to assignment -->
<start>
<locationX>50</locationX>
<locationY>50</locationY>
<connector>
<targetReference>Set_Result</targetReference>
</connector>
</start>
<!-- ✅ At least one element required -->
<assignments>
<name>Set_Result</name>
<label>Set Result</label>
<locationX>176</locationX>
<locationY>158</locationY>
<assignmentItems>
<assignToReference>out_Result</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Success</stringValue>
</value>
</assignmentItems>
</assignments>
</Flow>
When Creating Agentforce Flow Actions
- Define inputs matching Agent Script action
inputs: - Define outputs matching Agent Script action
outputs: - Add at least one assignment to set output values
- Connect start to assignment via
<connector> - Set status to Active for agent to invoke
Summary: Lessons Learned
Fault Connector Self-Reference
- Problem: Fault connector pointing to the same element
- Error: "The element cannot be connected to itself"
- Solution: Route fault connectors to a dedicated error handler element
XML Element Grouping
- Problem: Elements of same type scattered across the file
- Error: "Element X is duplicated at this location"
- Solution: Group ALL elements of same type together, in alphabetical order by type
Relationship Fields
- Problem: Querying
Parent.Fieldin Get Records - Solution: Two separate queries - child first, then parent by ID
Record-Triggered Flow Architecture
- Problem: Creating loops over triggered records
- Solution: Use
$Recorddirectly - platform handles batching
Deployment
- Problem: Using direct CLI commands
- Solution: Always use sf-deploy skill
$Record Context
- Problem: Confusing Flow's
$Recordwith Process Builder's$Record__c - Solution:
$Recordis single record, use without loops
Standard Objects for Testing
- Problem: Custom objects may not exist in target org
- Solution: When testing flow generation/deployment, prefer standard objects (Account, Contact, Opportunity, Task) for guaranteed deployability