Declarative vs Programmatic Solutions
Choosing between declarative (clicks) and programmatic (code) solutions is one of the most consequential decisions a CTA makes. This is not a binary choice — it is a spectrum, and the best architectures blend both strategically. The CTA Review Board expects you to articulate why you chose each approach, not just what you chose.
The Declarative-First Principle
Salesforce strongly advocates a “declarative first” philosophy: use clicks before code. But a CTA must understand when this principle should be followed and when it should be deliberately violated.
CTA Exam Signal
The review board does not want to hear “I used Flow because declarative is always better.” They want to hear “I chose Flow here because the logic is straightforward CRUD operations with simple branching, the admin team can maintain it, and the governor limit profile is favorable — but I chose Apex for the high-volume integration handler because Flow’s shared CPU time ceiling and per-transaction SOQL/DML limits would be exceeded.”
Flow Builder: The Declarative Powerhouse
Flow Builder is Salesforce’s primary declarative automation tool, and it has evolved dramatically. Understanding its capabilities and limits is essential.
Flow Types
| Flow Type | Trigger | Use Case | Context |
|---|---|---|---|
| Record-Triggered Flow | DML on a record | Before/after save automation | Runs in record transaction |
| Screen Flow | User interaction | Guided UI experiences | Interactive, supports LWC |
| Schedule-Triggered Flow | Time-based | Batch processing, reminders | Runs as Automated Process user |
| Platform Event-Triggered | Platform Event | Event-driven automation | Async, separate transaction |
| Autolaunched Flow | Invoked by code/process | Reusable logic modules | No UI, callable from Apex |
| Orchestration | Multi-step processes | Approval-like workflows | Long-running, multi-stage |
Record-Triggered Flow Execution Phases
Understanding the three phases is critical for avoiding recursion and governor limit issues:
flowchart TD
A[Record DML Operation] --> B[Before-Save Flow]
B --> C[Before Triggers - Apex]
C --> D[System / Custom Validations]
D --> E[Record Saved to DB]
E --> F[After Triggers - Apex]
F --> G[Assignment Rules / Auto-Response / Workflow Rules]
G --> H[After-Save Flow - Run Immediately]
H --> I[After-Save Flow - Run Asynchronously]
I --> J[Actions - Scheduled Paths]
style B fill:#2d6a4f,stroke:#1b4332,color:#fff
style H fill:#2d6a4f,stroke:#1b4332,color:#fff
style I fill:#2d6a4f,stroke:#1b4332,color:#fff
style J fill:#2d6a4f,stroke:#1b4332,color:#fff
Before-Save vs After-Save
Before-save flows can modify the triggering record without a DML statement (no governor limit hit). After-save flows require DML to update related records. Use before-save when you only need to modify the triggering record — it is faster and cheaper on limits.
Flow Governor Limits
These limits are where Flow’s declarative simplicity can become an architectural risk:
| Limit | Value | Why It Matters |
|---|---|---|
| Elements per interview | No hard cap (removed in API v57.0+) | Constrained by CPU time, not element count |
| SOQL queries per interview | 100 (shared with Apex transaction) | Flow Get Records counts against this |
| DML statements per interview | 150 (shared with Apex transaction) | Flow Create/Update/Delete counts here |
| CPU time | 10,000 ms sync / 60,000 ms async (shared) | Formula-heavy flows consume CPU — this is the practical ceiling |
| Heap size | 6 MB (shared) | Large collection variables can hit this |
| Screen Flow timeout | Governed by session timeout settings (configurable per profile) | Long-running screen flows expire based on org session policy |
The One Automation Per Object Rule
Salesforce best practice is to have one record-triggered flow per object per trigger event. This is architectural guidance, not a platform enforcement.
Why this matters:
- Multiple flows on the same object create unpredictable execution order
- Debugging becomes exponentially harder with multiple entry points
- Governor limits are shared across all automations in a transaction
- Cross-flow dependencies create hidden coupling
How to implement:
- Create one “master” record-triggered flow per object
- Use subflows for logical separation within the master flow
- Use Decision elements to route to the correct logic path
- Document the routing logic so admins can navigate it
flowchart TD
A[Account Record-Triggered Flow] --> B{Which Logic Path?}
B -->|"Territory Assignment"| C[Subflow: Territory Logic]
B -->|"Data Enrichment"| D[Subflow: Enrichment Logic]
B -->|"Notification"| E[Subflow: Alert Logic]
B -->|"Integration Sync"| F[Subflow: Integration Logic]
style A fill:#1a535c,stroke:#0d3b44,color:#fff
style B fill:#4ecdc4,stroke:#3ab5ad,color:#000
Apex: When Code Is the Right Choice
Apex is not a fallback — it is the right tool when declarative solutions introduce more complexity, risk, or maintenance burden than code.
When Apex Is Clearly Better
| Scenario | Why Apex Wins |
|---|---|
| Complex data transformations | Loops with conditional logic, maps, and sets are cleaner in code |
| High-volume processing | Batch Apex handles millions of records; Flow struggles past 50K |
| Multi-object transactions | Apex gives explicit transaction control and rollback |
| External service callouts | HttpRequest with retry logic, error handling, circuit breakers |
| Custom REST/SOAP endpoints | Apex web services for inbound integrations |
| Complex sharing calculations | Apex managed sharing for dynamic sharing rules |
| Performance-critical logic | Apex is faster; compiled vs interpreted Flow |
| Bulkification requirements | Apex triggers naturally bulkify; Flow requires careful design |
Apex Invocable Actions: The Bridge
Invocable Actions (@InvocableMethod) are the key integration point between declarative and programmatic:
@InvocableMethod(label='Calculate Territory Assignment' description='Assigns territory based on geo rules')public static List<Result> assignTerritory(List<Request> requests) { // Complex logic that would be painful in Flow // But callable from Flow as an Action}Why this pattern is architecturally significant:
- Admins can invoke complex logic without understanding Apex
- Business logic stays in code where it is testable and version-controlled
- Flow handles the orchestration; Apex handles the computation
- You can swap the implementation without changing the Flow
Custom LWC in Screen Flows
Screen Flows support embedding Lightning Web Components for custom UI:
Use cases:
- Complex data entry forms with real-time validation
- Interactive data visualization within a flow
- Custom lookup components with advanced filtering
- File upload with preview and metadata extraction
Trade-offs:
- Increases development complexity (LWC + Flow)
- Requires developer for the LWC portion
- Testing requires both Apex/LWC tests and Flow tests
- LWC must implement
FlowAttributeChangeEventfor data binding
Order of Execution
Understanding the full Salesforce order of execution is essential for debugging and predicting behavior. This is one of the most commonly tested CTA topics.
flowchart TD
A["1. Initial System Validation<br/>(required fields, field types, field lengths)"] --> B["2. Before-Save Record-Triggered Flows<br/>(can modify triggering record without DML)"]
B --> C["3. Before Triggers (Apex)<br/>(can modify fields on Trigger.new)"]
C --> D["4. Custom Validation Rules<br/>(+ duplicate rules evaluated)"]
D --> E["5. Record Saved to DB<br/>(NOT committed — in-memory only)"]
E --> F["6. After Triggers (Apex)<br/>(record has ID, can modify related records)"]
F --> G["7. Assignment Rules<br/>(Lead/Case assignment)"]
G --> H["8. Auto-Response Rules"]
H --> I["9. Workflow Rules<br/>(field updates, tasks, email alerts)"]
I --> J{"Workflow<br/>field update?"}
J -->|Yes| K["Re-run: Before + After Triggers<br/>(one additional cycle only)"]
K --> L["10. After-Save Flows — Run Immediately<br/>(can update related records)"]
J -->|No| L
L --> M["11. Entitlement Rules"]
M --> N["12. Roll-Up Summary Field Calculations<br/>(triggers parent object automation chain)"]
N --> O["13. Criteria-Based Sharing Re-evaluation"]
O --> P["14. DML Committed to Database"]
P --> Q["15. Post-Commit Logic<br/>(email sends, async Apex, outbound messages)"]
Q --> R["16. After-Save Flows — Run Asynchronously"]
R --> S["17. Scheduled Paths<br/>(future-dated flow paths)"]
style B fill:#2d6a4f,stroke:#1b4332,color:#fff
style C fill:#e76f51,stroke:#c45a3f,color:#fff
style F fill:#e76f51,stroke:#c45a3f,color:#fff
style I fill:#264653,stroke:#1d3640,color:#fff
style K fill:#9d0208,stroke:#6a040f,color:#fff
style L fill:#2d6a4f,stroke:#1b4332,color:#fff
style R fill:#2d6a4f,stroke:#1b4332,color:#fff
style S fill:#2d6a4f,stroke:#1b4332,color:#fff
style P fill:#f4a261,stroke:#d4823e,color:#000
Color Legend
Green = Flow execution points. Orange/Red = Apex trigger execution points. Dark blue = Workflow Rules (legacy but still active in many orgs). Red = Re-execution cycle caused by workflow field updates. Gold = Database commit point. Understanding where each fires in the sequence prevents conflicts between declarative and programmatic automations.
The Workflow Re-execution Trap
When workflow field updates modify a record, Salesforce re-runs before and after triggers one additional time. This does NOT re-run validation rules or before-save flows. This re-execution loop is a common source of unexpected behavior and governor limit consumption. Always account for this in governor limit budgeting.
Recursion and Re-evaluation
- Before-save flows do not cause re-evaluation (they modify the record in memory)
- After-save flows that update the triggering record cause re-entry through the order of execution
- Apex triggers can cause re-entry; use static variables to prevent infinite recursion
- Cross-object updates from roll-up summaries trigger the parent object’s automation chain
- Workflow field updates trigger one additional before + after trigger cycle (but NOT validation rules or before-save flows)
Flow vs Apex Capability Matrix
This matrix helps quickly identify which tool supports a given capability. Use it during CTA scenario analysis to justify technology choices.
| Capability | Flow | Apex | Verdict |
|---|---|---|---|
| Simple field updates | Yes — Before-Save Flow | Yes — but overkill | Flow |
| Complex data transforms | Limited — clunky loops | Yes — maps, sets, sorting | Apex |
| Bulk processing (50K+) | No — hits limits | Yes — Batch Apex | Apex |
| External callouts | External Services only | Yes — full HTTP control | Apex (or hybrid) |
| Transaction rollback | No savepoints | Yes — Database.setSavepoint | Apex |
| Automated testing | Limited — manual + debug | Yes — @isTest, CI/CD | Apex |
| Dynamic SOQL | No | Yes — Database.query | Apex |
| Custom REST endpoints | No | Yes — @RestResource | Apex |
| Platform event publish | Yes | Yes | Either |
| Scheduled execution | Yes — Scheduled Flows | Yes — Schedulable | Either (volume-dependent) |
| User-guided processes | Yes — Screen Flows | Possible but heavyweight | Flow |
| Approval processes | Yes — integrates natively | Requires custom implementation | Flow |
| Error handling | Basic Fault paths | Full try-catch with custom logic | Apex |
| Version control diffs | Metadata XML (hard to read) | Source code (meaningful diffs) | Apex |
Reading This Matrix
Where the verdict says “Flow,” that is a strong signal to go declarative. Where it says “Apex,” that is a strong signal to go programmatic. Where it says “Either,” use the volume and maintainability context from the decision guides to break the tie.
Decision Matrix: Flow vs Apex
flowchart TD
Start[New Automation Requirement] --> Q1{Is it simple field<br/>updates on the<br/>triggering record?}
Q1 -->|Yes| BSF[Before-Save Flow]
Q1 -->|No| Q2{Does it require<br/>complex data<br/>transformation?}
Q2 -->|Yes| Q3{Over 50K records?}
Q3 -->|Yes| BATCH[Batch Apex]
Q3 -->|No| Q4{Can Invocable<br/>Action bridge<br/>the gap?}
Q4 -->|Yes| HYBRID[Flow + Invocable Apex]
Q4 -->|No| APEX[Apex Trigger]
Q2 -->|No| Q5{Does it need<br/>user interaction?}
Q5 -->|Yes| Q6{Complex UI<br/>requirements?}
Q6 -->|Yes| LWC[LWC + Screen Flow]
Q6 -->|No| SF[Screen Flow]
Q5 -->|No| Q7{External system<br/>callout needed?}
Q7 -->|Yes| Q8{Simple REST call<br/>with retry?}
Q8 -->|Yes| EXTSERV[External Services + Flow]
Q8 -->|No| APEXCALLOUT[Apex with Callout]
Q7 -->|No| ASF[After-Save Flow]
style BSF fill:#2d6a4f,stroke:#1b4332,color:#fff
style ASF fill:#2d6a4f,stroke:#1b4332,color:#fff
style SF fill:#2d6a4f,stroke:#1b4332,color:#fff
style HYBRID fill:#f4a261,stroke:#d4823e,color:#000
style APEX fill:#e76f51,stroke:#c45a3f,color:#fff
style BATCH fill:#e76f51,stroke:#c45a3f,color:#fff
style LWC fill:#e76f51,stroke:#c45a3f,color:#fff
style APEXCALLOUT fill:#e76f51,stroke:#c45a3f,color:#fff
style EXTSERV fill:#f4a261,stroke:#d4823e,color:#000
Color Legend for Decision Tree
Green = Declarative (admin-maintainable). Orange = Hybrid (admin + developer). Red = Programmatic (developer-required). Use this to communicate staffing and maintenance implications to stakeholders.
Automation Strategy Architecture
In enterprise scenarios, automation is rarely a single tool. The best architectures blend Flows, Apex, and Platform Events into a layered strategy. This diagram shows how the three work together in a typical CTA scenario.
flowchart TB
subgraph UserLayer["User Transaction Layer (Synchronous)"]
direction LR
A["Record DML"] --> B["Before-Save Flow<br/>(field defaults, simple calcs)"]
B --> C["Apex Trigger<br/>(complex validation, cross-object)"]
C --> D["After-Save Flow<br/>(related record updates)"]
end
subgraph EventLayer["Event-Driven Layer (Asynchronous)"]
direction LR
E["Platform Event Published<br/>(from trigger or flow)"] --> F["PE-Triggered Flow<br/>(notification, logging)"]
E --> G["PE Apex Trigger<br/>(integration callout)"]
end
subgraph BatchLayer["Batch Processing Layer (Scheduled)"]
direction LR
H["Scheduled Flow<br/>(simple nightly updates)"] --> I["Results"]
J["Batch Apex<br/>(high-volume processing)"] --> K["Results"]
end
subgraph ConfigLayer["Configuration Layer"]
direction LR
L["Custom Metadata Types<br/>(routing rules, thresholds)"]
M["Custom Settings<br/>(feature flags, org config)"]
N["Named Credentials<br/>(integration endpoints)"]
end
D --> E
C --> E
L -.->|"reads config"| B
L -.->|"reads config"| C
N -.->|"auth"| G
style UserLayer fill:#f0f7f4,stroke:#2d6a4f
style EventLayer fill:#fef3e6,stroke:#f4a261
style BatchLayer fill:#fce4e4,stroke:#e76f51
style ConfigLayer fill:#eef0f5,stroke:#264653
CTA Presentation Strategy
Present your automation architecture in these four layers: synchronous user transactions, asynchronous event-driven processing, scheduled batch operations, and the configuration layer that makes everything environment-agnostic. This shows the review board you think about operational architecture, not just code.
Automation Strategy for CTA Scenarios
Assessment Checklist
When designing automation for a CTA scenario, evaluate each requirement against:
- Complexity: Can the logic be expressed in a Flow Decision element, or does it require nested loops with conditional maps?
- Volume: How many records will this process? Flow is fine for hundreds; Apex Batch for millions.
- Maintainability: Who will maintain this after go-live? If the customer has admins but not developers, Flow is operationally better.
- Performance: Is this in a synchronous user transaction? Apex is faster for complex operations.
- Testing: Flow testing is manual and limited. Apex testing is automated and CI/CD-friendly.
- Integration: Does this touch external systems? Apex gives you retry logic, circuit breakers, and error handling.
- Transaction control: Do you need explicit savepoints and rollbacks? Apex only.
Common CTA Scenario Patterns
| Scenario Pattern | Recommended Approach | Rationale |
|---|---|---|
| Lead assignment with territory rules | Flow + Invocable Apex | Admins manage routing rules; Apex handles geo-calculation |
| Order processing with inventory check | Apex trigger + platform events | Transaction integrity + async inventory notification |
| Customer onboarding wizard | Screen Flow + custom LWC | Guided experience with complex form inputs |
| Nightly data sync from ERP | Batch Apex + Schedulable | Volume, error handling, retry logic |
| Approval process with dynamic routing | Flow + Approval Process | Declarative routing with standard approval framework |
| Real-time lead scoring | Before-save Flow | Simple field calculation, no DML needed |
| Multi-object data validation | Apex trigger | Cross-object queries and validation in single transaction |
Anti-Patterns to Avoid
Common Automation Anti-Patterns
- Multiple record-triggered flows on the same object — Creates unpredictable behavior and debugging nightmares
- Flow loops that perform DML inside the loop — Governor limit violations waiting to happen
- Using Apex for simple field updates — Over-engineering when a before-save Flow suffices
- Hardcoded IDs in Flows or Apex — Fails across environments; use Custom Metadata Types
- No error handling in Flows — Unhandled faults cause cryptic user errors
- Mixing Process Builder + Flow + Workflow Rules — Legacy mess; consolidate to Flow (Process Builder is deprecated)
- Apex triggers without bulkification — Works in dev, fails in production with data loader operations
Trade-Offs Summary
| Factor | Flow (Declarative) | Apex (Programmatic) |
|---|---|---|
| Development speed | Faster for simple logic | Faster for complex logic |
| Maintenance | Admin-friendly | Requires developer |
| Testing | Manual, limited | Automated, CI/CD-ready |
| Performance | Good for simple ops | Better for complex ops |
| Governor limits | Shared, harder to optimize | Shared, easier to optimize |
| Version control | Metadata API export | Native source tracking |
| Debugging | Flow Debug UI (limited) | Debug logs, breakpoints, stack traces |
| Reusability | Subflows | Apex classes, interfaces, inheritance |
| Error handling | Fault paths (basic) | Try-catch with custom logic |
| Bulk operations | Requires careful design | Natural with collections |
Related Topics
- Modern Platform Features — OmniStudio, External Services, and other declarative alternatives
- Decision Guides — Visual flowcharts for common architecture decisions
- Best Practices — Solution architecture patterns and anti-patterns
- Trade-Offs — Deeper analysis of declarative vs programmatic trade-offs
- Testing Strategy — How testing requirements influence the Flow vs Apex decision
Sources
- Salesforce Developer Documentation: Flow Builder
- Salesforce Architects: Automation Guide
- Salesforce Help: Order of Execution
- Salesforce Ben: One Flow Per Object Best Practice
- Salesforce Ben: Complete Guide to Flow Limits
- Salesforce Trailhead: Flow Builder Modules
- Salesforce Trailhead: Optimize Flow Limits & Best Practices
- SFDC Developers: When to Use Flow vs Apex
- SFDC Developers: Order of Execution Guide
- CTA Study Groups: Community best practices for automation strategy