Skip to content

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 TypeTriggerUse CaseContext
Record-Triggered FlowDML on a recordBefore/after save automationRuns in record transaction
Screen FlowUser interactionGuided UI experiencesInteractive, supports LWC
Schedule-Triggered FlowTime-basedBatch processing, remindersRuns as Automated Process user
Platform Event-TriggeredPlatform EventEvent-driven automationAsync, separate transaction
Autolaunched FlowInvoked by code/processReusable logic modulesNo UI, callable from Apex
OrchestrationMulti-step processesApproval-like workflowsLong-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:

LimitValueWhy It Matters
Elements per interviewNo hard cap (removed in API v57.0+)Constrained by CPU time, not element count
SOQL queries per interview100 (shared with Apex transaction)Flow Get Records counts against this
DML statements per interview150 (shared with Apex transaction)Flow Create/Update/Delete counts here
CPU time10,000 ms sync / 60,000 ms async (shared)Formula-heavy flows consume CPU — this is the practical ceiling
Heap size6 MB (shared)Large collection variables can hit this
Screen Flow timeoutGoverned 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

ScenarioWhy Apex Wins
Complex data transformationsLoops with conditional logic, maps, and sets are cleaner in code
High-volume processingBatch Apex handles millions of records; Flow struggles past 50K
Multi-object transactionsApex gives explicit transaction control and rollback
External service calloutsHttpRequest with retry logic, error handling, circuit breakers
Custom REST/SOAP endpointsApex web services for inbound integrations
Complex sharing calculationsApex managed sharing for dynamic sharing rules
Performance-critical logicApex is faster; compiled vs interpreted Flow
Bulkification requirementsApex 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 FlowAttributeChangeEvent for 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.

CapabilityFlowApexVerdict
Simple field updatesYes — Before-Save FlowYes — but overkillFlow
Complex data transformsLimited — clunky loopsYes — maps, sets, sortingApex
Bulk processing (50K+)No — hits limitsYes — Batch ApexApex
External calloutsExternal Services onlyYes — full HTTP controlApex (or hybrid)
Transaction rollbackNo savepointsYes — Database.setSavepointApex
Automated testingLimited — manual + debugYes — @isTest, CI/CDApex
Dynamic SOQLNoYes — Database.queryApex
Custom REST endpointsNoYes — @RestResourceApex
Platform event publishYesYesEither
Scheduled executionYes — Scheduled FlowsYes — SchedulableEither (volume-dependent)
User-guided processesYes — Screen FlowsPossible but heavyweightFlow
Approval processesYes — integrates nativelyRequires custom implementationFlow
Error handlingBasic Fault pathsFull try-catch with custom logicApex
Version control diffsMetadata 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:

  1. Complexity: Can the logic be expressed in a Flow Decision element, or does it require nested loops with conditional maps?
  2. Volume: How many records will this process? Flow is fine for hundreds; Apex Batch for millions.
  3. Maintainability: Who will maintain this after go-live? If the customer has admins but not developers, Flow is operationally better.
  4. Performance: Is this in a synchronous user transaction? Apex is faster for complex operations.
  5. Testing: Flow testing is manual and limited. Apex testing is automated and CI/CD-friendly.
  6. Integration: Does this touch external systems? Apex gives you retry logic, circuit breakers, and error handling.
  7. Transaction control: Do you need explicit savepoints and rollbacks? Apex only.

Common CTA Scenario Patterns

Scenario PatternRecommended ApproachRationale
Lead assignment with territory rulesFlow + Invocable ApexAdmins manage routing rules; Apex handles geo-calculation
Order processing with inventory checkApex trigger + platform eventsTransaction integrity + async inventory notification
Customer onboarding wizardScreen Flow + custom LWCGuided experience with complex form inputs
Nightly data sync from ERPBatch Apex + SchedulableVolume, error handling, retry logic
Approval process with dynamic routingFlow + Approval ProcessDeclarative routing with standard approval framework
Real-time lead scoringBefore-save FlowSimple field calculation, no DML needed
Multi-object data validationApex triggerCross-object queries and validation in single transaction

Anti-Patterns to Avoid

Common Automation Anti-Patterns

  1. Multiple record-triggered flows on the same object — Creates unpredictable behavior and debugging nightmares
  2. Flow loops that perform DML inside the loop — Governor limit violations waiting to happen
  3. Using Apex for simple field updates — Over-engineering when a before-save Flow suffices
  4. Hardcoded IDs in Flows or Apex — Fails across environments; use Custom Metadata Types
  5. No error handling in Flows — Unhandled faults cause cryptic user errors
  6. Mixing Process Builder + Flow + Workflow Rules — Legacy mess; consolidate to Flow (Process Builder is deprecated)
  7. Apex triggers without bulkification — Works in dev, fails in production with data loader operations

Trade-Offs Summary

FactorFlow (Declarative)Apex (Programmatic)
Development speedFaster for simple logicFaster for complex logic
MaintenanceAdmin-friendlyRequires developer
TestingManual, limitedAutomated, CI/CD-ready
PerformanceGood for simple opsBetter for complex ops
Governor limitsShared, harder to optimizeShared, easier to optimize
Version controlMetadata API exportNative source tracking
DebuggingFlow Debug UI (limited)Debug logs, breakpoints, stack traces
ReusabilitySubflowsApex classes, interfaces, inheritance
Error handlingFault paths (basic)Try-catch with custom logic
Bulk operationsRequires careful designNatural with collections

Sources