Flows Architecture & Best Practices

0/1 in this phase0/41 across the roadmap

📖 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:

  1. 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
  2. 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
  3. Schedule-Triggered Flow — Runs at specific times

    • Cron-based scheduling (daily, weekly, etc.)
    • Processes batches of records
    • Use for: reminders, cleanup, periodic calculations
  4. 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
  5. 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:

  1. Use before-save flows for simple field updates (no DML cost)
  2. Name elements descriptively (not "Decision1" — use "Check_If_High_Priority")
  3. Use subflows for reusable logic
  4. Bulkify by using collection variables, not individual operations
  5. Document flow purpose and decisions with Description fields
  6. Version control: export Flows as metadata in Git

💻 Code Example

codeTap to expand ⛶
1// Invocable Apex — Bridge between Flow and Apex
2
3// 1. Invocable Method — Called from Flow
4public class FlowActions {
5
6 // Single action called from Flow
7 @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>();
14
15 for (Request req : requests) {
16 try {
17 // Build notification
18 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);
23
24 // Send to recipients
25 notification.send(new Set<String>{req.recipientId});
26
27 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 }
40
41 // Input parameters (from Flow)
42 public class Request {
43 @InvocableVariable(required=true label='Notification Title')
44 public String title;
45
46 @InvocableVariable(required=true label='Notification Body')
47 public String body;
48
49 @InvocableVariable(required=true label='Target Record ID')
50 public Id targetRecordId;
51
52 @InvocableVariable(required=true label='Recipient User ID')
53 public Id recipientId;
54 }
55
56 // Output parameters (back to Flow)
57 public class Result {
58 @InvocableVariable(label='Success')
59 public Boolean isSuccess;
60
61 @InvocableVariable(label='Message')
62 public String message;
63 }
64
65 private static Id getNotificationTypeId() {
66 return [
67 SELECT Id FROM CustomNotificationType
68 WHERE DeveloperName = 'Custom_Alert' LIMIT 1
69 ].Id;
70 }
71}
72
73// 2. Invocable Method for complex calculations
74public class FlowCalculations {
75
76 @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>();
83
84 for (ForecastRequest req : requests) {
85 // Complex calculation that would be painful in Flow
86 List<Opportunity> pipeline = [
87 SELECT Amount, Probability, StageName
88 FROM Opportunity
89 WHERE AccountId = :req.accountId
90 AND IsClosed = false
91 AND CloseDate <= :req.forecastEndDate
92 ];
93
94 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 }
102
103 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 }
111
112 public class ForecastRequest {
113 @InvocableVariable(required=true label='Account ID')
114 public Id accountId;
115
116 @InvocableVariable(required=true label='Forecast End Date')
117 public Date forecastEndDate;
118 }
119
120 public class ForecastResult {
121 @InvocableVariable(label='Weighted Forecast')
122 public Decimal weightedForecast;
123
124 @InvocableVariable(label='Best Case Forecast')
125 public Decimal bestCaseForecast;
126
127 @InvocableVariable(label='Pipeline Count')
128 public Integer pipelineCount;
129 }
130}
131
132// 3. Custom Flow Screen Component (LWC)
133// flowDataTable.js — Custom LWC for use in Screen Flows
134// import { LightningElement, api } from 'lwc';
135// export default class FlowDataTable extends LightningElement {
136// @api tableData; // Input from Flow
137// @api selectedRecords; // Output to Flow
138//
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 variable
148// this.dispatchEvent(new FlowAttributeChangeEvent(
149// 'selectedRecords', this.selectedRecords
150// ));
151// }
152// }

🏋️ Practice Exercise

Flow Practice:

  1. Build a Screen Flow for employee onboarding that creates Account, Contact, and Case records
  2. Create a Record-Triggered Flow (before-save) that sets default values on new Opportunities
  3. Create a Record-Triggered Flow (after-save) that creates a Task when a Case is escalated
  4. Build a Scheduled Flow that sends reminder emails for Opportunities closing in 7 days
  5. Create an Invocable Apex method that performs a complex calculation and call it from a Flow
  6. Build a subflow for address validation and use it in 3 different parent Flows
  7. Create a Platform Event-Triggered Flow that processes incoming order events
  8. Build a custom LWC Screen Component for use in a Screen Flow (e.g., data table selector)
  9. Design a Flow that replaces an existing Process Builder + Workflow Rule combination
  10. 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.