Flows Architecture & Best Practices
📖 Concept
Salesforce's declarative-first philosophy means you should always consider Flow before writing Apex. Modern Flows are powerful enough for most business automation, and they're maintainable by admins — not just developers.
Flow Types:
Screen Flow — User-interactive wizards
- Multi-step forms, guided processes
- Input validation, conditional screens
- Can include custom LWC components
- Use for: application forms, guided data entry, wizards
Record-Triggered Flow — Replaces Process Builder & Workflow Rules
- Fires on record create, update, delete
- Before-save (fast, no DML needed) or After-save (for related records)
- Replaces: Workflow Rules, Process Builder
- Use for: field updates, related record creation, validation
Schedule-Triggered Flow — Runs at specific times
- Cron-based scheduling (daily, weekly, etc.)
- Processes batches of records
- Use for: reminders, cleanup, periodic calculations
Auto-Launched Flow (No Trigger) — Called from Apex, other Flows
- Invoked programmatically or from other automations
- Can accept input/output variables
- Use for: reusable sub-processes
Platform Event-Triggered Flow — React to events
- Triggered by Platform Events
- Each gets its own transaction
- Use for: event-driven automation
Flow vs Apex — when to use which:
Use Flow when:
✓ Business logic can change frequently (admin-maintainable)
✓ Simple field updates and record creation
✓ Screen-based user interactions
✓ The logic doesn't require complex collections or algorithms
Use Apex when:
✓ Complex business logic with multiple conditions
✓ Heavy data processing (10,000+ records)
✓ External API callouts with complex error handling
✓ Unit testing is critical (Flows are harder to unit test)
✓ Performance-sensitive operations
Before-Save vs After-Save Record-Triggered Flows:
- Before-Save: Runs before the record is committed. Can modify the triggering record's fields directly WITHOUT DML (like a before trigger). Fastest option for simple field updates.
- After-Save: Runs after the record is committed. Has an Id. Can create/update related records. Can call Apex actions. More resource-intensive.
Flow Best Practices:
- Use before-save flows for simple field updates (no DML cost)
- Name elements descriptively (not "Decision1" — use "Check_If_High_Priority")
- Use subflows for reusable logic
- Bulkify by using collection variables, not individual operations
- Document flow purpose and decisions with Description fields
- Version control: export Flows as metadata in Git
💻 Code Example
1// Invocable Apex — Bridge between Flow and Apex23// 1. Invocable Method — Called from Flow4public class FlowActions {56 // Single action called from Flow7 @InvocableMethod(8 label='Send Custom Notification'9 description='Sends a custom notification to specified users'10 category='Notifications'11 )12 public static List<Result> sendNotification(List<Request> requests) {13 List<Result> results = new List<Result>();1415 for (Request req : requests) {16 try {17 // Build notification18 Messaging.CustomNotification notification = new Messaging.CustomNotification();19 notification.setTitle(req.title);20 notification.setBody(req.body);21 notification.setNotificationTypeId(getNotificationTypeId());22 notification.setTargetId(req.targetRecordId);2324 // Send to recipients25 notification.send(new Set<String>{req.recipientId});2627 Result res = new Result();28 res.isSuccess = true;29 res.message = 'Notification sent successfully';30 results.add(res);31 } catch (Exception e) {32 Result res = new Result();33 res.isSuccess = false;34 res.message = e.getMessage();35 results.add(res);36 }37 }38 return results;39 }4041 // Input parameters (from Flow)42 public class Request {43 @InvocableVariable(required=true label='Notification Title')44 public String title;4546 @InvocableVariable(required=true label='Notification Body')47 public String body;4849 @InvocableVariable(required=true label='Target Record ID')50 public Id targetRecordId;5152 @InvocableVariable(required=true label='Recipient User ID')53 public Id recipientId;54 }5556 // Output parameters (back to Flow)57 public class Result {58 @InvocableVariable(label='Success')59 public Boolean isSuccess;6061 @InvocableVariable(label='Message')62 public String message;63 }6465 private static Id getNotificationTypeId() {66 return [67 SELECT Id FROM CustomNotificationType68 WHERE DeveloperName = 'Custom_Alert' LIMIT 169 ].Id;70 }71}7273// 2. Invocable Method for complex calculations74public class FlowCalculations {7576 @InvocableMethod(77 label='Calculate Revenue Forecast'78 description='Calculates revenue forecast based on pipeline'79 category='Calculations'80 )81 public static List<ForecastResult> calculateForecast(List<ForecastRequest> requests) {82 List<ForecastResult> results = new List<ForecastResult>();8384 for (ForecastRequest req : requests) {85 // Complex calculation that would be painful in Flow86 List<Opportunity> pipeline = [87 SELECT Amount, Probability, StageName88 FROM Opportunity89 WHERE AccountId = :req.accountId90 AND IsClosed = false91 AND CloseDate <= :req.forecastEndDate92 ];9394 Decimal weighted = 0;95 Decimal bestCase = 0;96 for (Opportunity opp : pipeline) {97 if (opp.Amount != null && opp.Probability != null) {98 weighted += opp.Amount * (opp.Probability / 100);99 bestCase += opp.Amount;100 }101 }102103 ForecastResult result = new ForecastResult();104 result.weightedForecast = weighted;105 result.bestCaseForecast = bestCase;106 result.pipelineCount = pipeline.size();107 results.add(result);108 }109 return results;110 }111112 public class ForecastRequest {113 @InvocableVariable(required=true label='Account ID')114 public Id accountId;115116 @InvocableVariable(required=true label='Forecast End Date')117 public Date forecastEndDate;118 }119120 public class ForecastResult {121 @InvocableVariable(label='Weighted Forecast')122 public Decimal weightedForecast;123124 @InvocableVariable(label='Best Case Forecast')125 public Decimal bestCaseForecast;126127 @InvocableVariable(label='Pipeline Count')128 public Integer pipelineCount;129 }130}131132// 3. Custom Flow Screen Component (LWC)133// flowDataTable.js — Custom LWC for use in Screen Flows134// import { LightningElement, api } from 'lwc';135// export default class FlowDataTable extends LightningElement {136// @api tableData; // Input from Flow137// @api selectedRecords; // Output to Flow138//139// columns = [140// { label: 'Name', fieldName: 'Name' },141// { label: 'Industry', fieldName: 'Industry' },142// { label: 'Revenue', fieldName: 'AnnualRevenue', type: 'currency' }143// ];144//145// handleRowSelection(event) {146// this.selectedRecords = event.detail.selectedRows;147// // Dispatch FlowAttributeChangeEvent to update Flow variable148// this.dispatchEvent(new FlowAttributeChangeEvent(149// 'selectedRecords', this.selectedRecords150// ));151// }152// }
🏋️ Practice Exercise
Flow Practice:
- Build a Screen Flow for employee onboarding that creates Account, Contact, and Case records
- Create a Record-Triggered Flow (before-save) that sets default values on new Opportunities
- Create a Record-Triggered Flow (after-save) that creates a Task when a Case is escalated
- Build a Scheduled Flow that sends reminder emails for Opportunities closing in 7 days
- Create an Invocable Apex method that performs a complex calculation and call it from a Flow
- Build a subflow for address validation and use it in 3 different parent Flows
- Create a Platform Event-Triggered Flow that processes incoming order events
- Build a custom LWC Screen Component for use in a Screen Flow (e.g., data table selector)
- Design a Flow that replaces an existing Process Builder + Workflow Rule combination
- Document all Flows in your org and identify which could be consolidated
⚠️ Common Mistakes
Using after-save for simple field updates — before-save flows don't need DML and are much more efficient. Use after-save only when you need to create/update related records
Not bulkifying Flows — Flows that process records individually (no collection variables) fail or hit limits with bulk data operations
Creating multiple Record-Triggered Flows on the same object — combine them into one Flow with Decision elements. Salesforce evaluates order but it can be unpredictable
Not testing Flows with bulk data — use Data Loader to insert 200 records and verify the Flow handles them without errors
Building complex algorithms in Flow — if the logic requires nested loops, complex math, or map lookups, use Invocable Apex instead
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Flows Architecture & Best Practices. Login to unlock this feature.