On this page
A team asks for this Opportunity automation:
- Set a classification field before the record is saved.
- When an Opportunity becomes Closed Won, create onboarding work.
- Calculate a service deadline using business hours and holidays.
- Notify a fulfillment system without slowing the user’s save.
- Work for one user edit, a 200-record API transaction, and a future nightly data load.
One developer says, “Apex is safer.” An admin says, “Salesforce recommends Flow.” Both answers are incomplete.
The real decision is not whether Flow or Apex can perform one step. It is which design keeps the entire transaction understandable, bulk-safe, testable, supportable, and within the team’s operating model as the object becomes busier.
This guide gives you a repeatable way to choose Flow, Apex, or a hybrid design. For interview-style practice after learning the framework, see our scenario-based Salesforce interview questions and senior Apex trigger questions.
“Can Flow do it?” is the wrong first question
Flow and Apex overlap. Both can evaluate conditions, query records, change data, and participate in record-triggered automation. A feature checklist therefore produces weak architecture decisions.
Start with the business transaction:
- What starts it? A user save, API load, platform event, schedule, screen interaction, or another process?
- What must finish before commit? Which validations or field values must be synchronous?
- What can happen after commit? Which work can fail or retry without rolling back the user’s save?
- What is the batch shape? One record, 200 records in a transaction, thousands in a data load, or a continuous high-volume stream?
- What else runs on the object? Flow, Apex, managed packages, rollups, validation, assignment, and cross-object updates share the transaction.
- Who owns the automation? Who can safely change, test, deploy, monitor, and repair it at 2 a.m.?
Salesforce Architects currently frames the choice around automation density: the quantity of automation, record volume, and downstream dependency sprawl on an object. Low-density automation often fits record-triggered Flow; medium density can fit Flow with Invocable Apex; high-density, high-volume, or deeply coupled automation can justify an Apex trigger architecture.
Treat those labels as an architectural heuristic, not a substitute for measuring your org. A single Flow that performs many queries and cross-object updates can be denser than several tightly conditioned flows.
Evaluate six decision factors
Score the requirement across these factors before selecting a tool.
1. Transaction timing and rollback
Ask what must happen before the record commits.
- A same-record field calculation usually belongs in a before-save path.
- Simple related-record work can run after save.
- Work that can tolerate eventual completion may belong after commit in an asynchronous path or another durable processing mechanism.
- Logic requiring savepoints, explicit rollback, partial-success DML, or fine-grained transaction recovery points toward Apex.
Moving work asynchronously changes the business behavior. The user’s save can succeed even if later processing fails, so the design needs durable status, monitoring, and recovery. “Async” is not merely a performance switch.
2. Data shape and algorithm complexity
Flow is expressive for clear decisions, assignments, record operations, subflows, notifications, and orchestration. It becomes harder to reason about when the requirement depends on complex grouping, deduplication, relationship graphs, reusable algorithms, or data structures such as maps and sets.
Apex is a stronger fit when the algorithm needs:
- efficient correlation of several record collections;
- reusable domain logic across multiple entry points;
- complex calculations or platform libraries unavailable in Flow;
- fine control over query shape and DML;
- state shared within one transaction to avoid repeated expensive work.
Do not move to Apex only because a Flow canvas is large. First check whether the Flow has unclear responsibilities, imprecise entry criteria, duplicated logic, or missing subflows. A large diagram can be a design smell rather than a language limitation.
3. Volume and automation density
Flow and Apex share the platform transaction and its limits. Apex does not create unlimited CPU, queries, or DML. Flow does not make an inefficient data model free.
Test with realistic transaction sizes and include every automation that the save activates. Look for:
- repeated queries or updates across branches;
- loops that build one-record-at-a-time work;
- updates that re-enter the original object;
- parent-child cascades;
- managed-package automation;
- batch or API loads that behave differently from a single UI edit.
As density grows, the value of one primary entry point, explicit ordering, reusable services, and measurable performance also grows.
4. Security and execution context
Define the intended identity and access model before implementation.
Flow execution context varies by flow type and configuration. Apex data access also requires deliberate sharing, object-permission, and field-permission decisions. A class declared with sharing addresses record sharing but does not by itself prove that every object and field operation is authorized.
Document whether each operation should respect the running user’s access or execute as a tightly controlled system process. Test positive and negative personas. A design is not secure merely because it is declarative, and it is not insecure merely because it contains code.
For a deeper access model, use the SFX Salesforce Security and Sharing guide.
5. Change ownership and maintainability
Choose for the team that will own the automation, not the person who can build it fastest today.
Flow can make straightforward business routing visible to trained admins. Apex gives developers strong abstractions, code review, unit testing, and precise refactoring tools. Either can become unmaintainable:
- a mega-flow with mixed responsibilities, vague labels, and hidden re-entry;
- a trigger with business rules buried in conditionals and no handler boundaries;
- several Flow and Apex entry points whose relative order nobody owns.
Both Flow and Apex are metadata. Keep both in source control, review their changes, deploy them through the same release discipline, and document why the architecture was chosen.
6. Testing, observability, and recovery
Before selecting a tool, write down how you will prove and operate the behavior.
Ask:
- Can the logic be tested without changing real customer data?
- Can tests cover each branch, boundary, bulk case, permission case, and failure path?
- Where will an operator see a failure?
- Can failed asynchronous work be retried safely?
- How will support correlate one business record with its automation runs and downstream effects?
- What is the disable or rollback plan?
If the answers depend on manually clicking through production, the design is not ready—regardless of the tool.
Flow, Apex, or hybrid: a practical matrix
Use this as a starting point, then confirm it against your org’s density and operating model.
| Requirement characteristic | Flow is usually stronger | Apex is usually stronger | Hybrid opportunity |
|---|---|---|---|
| Same-record field update | Before-save record-triggered Flow | Before trigger when part of a larger trigger architecture | Keep simple assignment visible in Flow unless one-entry-point governance says otherwise |
| Straightforward decisions and related records | Clear visual orchestration | Possible, but may add unnecessary code ownership | Flow can call a focused Apex action for one complex step |
| Dynamic record-specific future action | Scheduled path | Custom scheduling requires more machinery | Flow schedules; Apex performs specialized work |
| Complex maps, sets, grouping, or algorithms | Can become cumbersome | Strong fit | Flow owns routing; bulk-safe Invocable Apex computes |
| Savepoints, rollback, or partial-success DML | Limited transaction control | Strong fit | Apex service owns the transaction boundary |
| High automation density and dependency sprawl | Requires careful choreography | Strong trigger framework can centralize control | Choose one primary entry point and call bounded services |
| Simple email or task creation | Strong fit | Usually unnecessary | Apex can supply data if the calculation is complex |
| External callout after a record save | Async Flow path can fit simple needs | Queueable or event-driven Apex gives more control | Flow creates durable intent; Apex or integration layer dispatches |
| Logic reused by Flow, UI, API, and jobs | Subflow only covers Flow consumers | Reusable service class is stronger | Expose one tested service through a narrow invocable adapter |
| Sophisticated error classification and retry | Possible but can become opaque | Strong fit with explicit states and recovery | Flow handles user-facing path; Apex handles technical recovery |
The matrix is not a license to mix tools casually. Salesforce Architects recommends one primary automation entry point per object as density grows. A hybrid design can still follow that rule: Flow can own the entry and choreography while Invocable Apex performs a bounded computation, or an Apex trigger can own the entry and call service classes.
When Flow is the better fit
Prefer Flow when the process is low to moderate in complexity, its route should remain visible to admins, and the platform elements express the logic clearly.
Strong Flow candidates include:
- setting fields on the triggering record before save;
- creating a small, predictable set of related records after save;
- sending a standard notification or email alert;
- routing work through understandable decisions;
- launching approvals or reusable subflows;
- scheduling a record-specific action relative to a date;
- orchestrating a process whose specialist calculation is delegated to one Apex action.
Flow design rules that matter more than the tool choice
- Use precise entry conditions so irrelevant updates do not enter the process.
- Use before-save Flow for same-record field changes that do not require related-record work.
- Keep record lookups and DML out of per-record loop behavior; operate on collections.
- Use clear element API names and descriptions that explain intent.
- Add fault paths that produce an owned, actionable outcome.
- Control relative order through Flow Trigger Explorer when multiple record-triggered flows are part of the chosen entry architecture.
- Do not build one massive flow merely to claim there is “one flow per object.” Organize responsibilities while preserving one governed entry strategy.
Flow’s accessibility is valuable only when the team applies engineering discipline. Easy editing without review can increase production risk.
When Apex is the better fit
Prefer Apex when the requirement needs capabilities or control that would be inefficient, opaque, or fragile in Flow.
Strong Apex candidates include:
- high-density automation that needs a governed trigger-handler architecture;
- complex bulk transformations using maps, sets, grouping, or deduplication;
- transaction savepoints, rollbacks, and partial-success behavior;
- reusable domain logic shared across triggers, APIs, jobs, and UI controllers;
- sophisticated concurrency, locking, recursion, or idempotency controls;
- specialized platform libraries such as advanced cryptography or business-hours calculations;
- large-volume processing that needs carefully optimized queries and algorithms;
- technical error classification, retry state, and operational recovery.
Apex must earn its complexity. A good implementation includes:
- a minimal trigger that delegates to tested handlers;
- bulk-safe collections and queries outside loops;
- meaningful-change detection;
- explicit sharing and CRUD/FLS decisions;
- negative, bulk, permission, recursion, and failure tests;
- clear ownership and production monitoring.
Moving a poorly designed Flow into poorly designed Apex only changes the format of the incident.
Use the hybrid pattern deliberately
The hybrid pattern is useful when business orchestration should stay visible in Flow but one step requires programmatic strength.
The boundary should be narrow:
- Flow owns when the capability runs and which business route is selected.
- Apex owns one cohesive calculation or technical operation.
- Inputs and outputs form a stable contract.
- The Apex method accepts and returns collections because Flow can invoke it in bulk contexts.
- The Apex service does not hide unrelated routing or surprise DML.
Here is an illustrative bulk-safe Invocable Apex action that calculates an SLA due time using the org’s default Business Hours. It performs one configuration query, preserves input order, and does no DML.
public with sharing class OpportunitySlaCalculator {
public class Request {
@InvocableVariable(required=true)
public Id opportunityId;
@InvocableVariable(required=true)
public Datetime startAt;
@InvocableVariable(required=true)
public Long durationMilliseconds;
}
public class Response {
@InvocableVariable
public Id opportunityId;
@InvocableVariable
public Datetime dueAt;
@InvocableVariable
public String errorCode;
}
@InvocableMethod(label='Calculate Opportunity SLA Due Dates')
public static List<Response> calculate(List<Request> requests) {
List<BusinessHours> schedules = [
SELECT Id
FROM BusinessHours
WHERE IsDefault = true
WITH SYSTEM_MODE
LIMIT 1
];
List<Response> results = new List<Response>();
for (Request input : requests) {
Response output = new Response();
output.opportunityId = input.opportunityId;
if (schedules.isEmpty()) {
output.errorCode = 'DEFAULT_BUSINESS_HOURS_MISSING';
} else if (
input.startAt == null ||
input.durationMilliseconds == null ||
input.durationMilliseconds < 0
) {
output.errorCode = 'INVALID_INPUT';
} else {
output.dueAt = BusinessHours.add(
schedules[0].Id,
input.startAt,
input.durationMilliseconds
);
}
results.add(output);
}
return results;
}
}
The system-mode query is intentional and limited to setup configuration; document that decision. If the action reads or changes customer records, enforce the approved user or system access contract explicitly. Add Apex tests for empty, invalid, single, and bulk inputs, and verify the Flow handles each returned error code.
Do not make one invocable action a back door to an entire hidden process. If most of the orchestration has moved into Apex, reconsider whether Apex should become the object’s primary entry architecture.
Worked example: Closed Won onboarding
Return to the Opportunity requirement from the opening. A defensible solution might be:
| Requirement | Decision | Reasoning and trade-off |
|---|---|---|
Set Customer_Tier__c from stable fields |
Before-save Flow | Same-record update, readable rule, no extra DML |
| Detect the transition to Closed Won | Record-triggered Flow entry criteria | Business transition remains visible and avoids running on unrelated edits |
| Calculate an SLA due time using holidays | Invocable Apex action | BusinessHours calculation is reusable, bulk-safe, and isolated behind a narrow contract |
| Create onboarding Task and Project records | After-save Flow | Simple related-record orchestration with fault paths |
| Notify fulfillment | Durable asynchronous handoff | The user’s save should not wait for a remote service; failures need status and replay |
| Prepare for large nightly loads | Performance test the complete transaction | The future volume may change the architecture; do not assume a UI-scale design will hold |
This design is not automatically correct for every org. Before approving it, inspect all Opportunity automation. If Opportunity already has high trigger density, several packages, deep cross-object updates, or a mature Apex framework, adding Flow as another entry point may reduce control. The same business steps may belong behind the established Apex entry architecture.
Define failure behavior before activation
For each step, decide:
- Does failure block the Opportunity save?
- If the save succeeds, where is incomplete onboarding recorded?
- Who receives the alert?
- Is retry safe, and how is duplicate work prevented?
- What evidence proves fulfillment received the request?
- How can operations pause the new behavior without disabling unrelated automation?
The answer may lead to a small status object or integration-work record rather than relying only on an email from a Flow fault path.
Test and operate the whole transaction
Do not test Flow and Apex as if they run in separate worlds. A record save can execute validation, before-save Flow, Apex triggers, after-save Flow, package code, rollups, and downstream updates in one transaction.
Test layers
- Apex unit tests: Prove the invocable or service logic for bulk, boundary, negative, and permission cases.
- Flow tests and debugger: Use Flow Builder’s supported debugging and Test Mode capabilities for the relevant flow type. Automated Test Mode features can be beta or availability-dependent, so verify the current feature status in the target org.
- Transaction integration tests: Save realistic records and assert the combined Flow, Apex, validation, and related-record outcomes.
- Volume tests: Use representative API transaction sizes and data distributions in a sandbox.
- Persona tests: Verify least-privileged users, integration users, and administrators separately.
- Failure tests: Force missing configuration, invalid data, asynchronous failure, and downstream unavailability.
Test “does not run” behavior too. An update to an unrelated field should not execute expensive Closed Won logic. A second save should not create duplicate onboarding records. A bulk transaction should produce the same business outcomes as individual saves.
Production signals
Monitor:
- Flow error records and owned fault notifications;
- Apex exceptions and failed asynchronous jobs;
- transaction latency and limit consumption in representative paths;
- duplicate Tasks, Projects, or outbound requests;
- queue age and oldest incomplete onboarding item;
- success rate from Closed Won to completed onboarding;
- deployment and activation history for both Flow and Apex metadata.
Technical success is not enough. Reconcile business outcomes: every eligible Closed Won Opportunity should have exactly the expected onboarding state and fulfillment acknowledgement.
Release and rollback
Version Flow and Apex together when they form one contract. Deploy the invocable action before activating a Flow version that calls it. Preserve a compatible action signature while any active Flow version depends on it.
Use a governed feature switch or entry criteria when staged activation is required. Define whether rollback means activating the previous Flow version, deploying last-known-good Apex, disabling a route, or repairing data. None of those automatically reverses emails, callouts, or records already created.
Architecture review checklist
Before approving Flow, Apex, or a hybrid design, answer these questions:
- What is the primary automation entry point for this object?
- What is the object’s current automation density and dependency graph?
- Which work must be synchronous, and which work may happen after commit?
- Does the design behave correctly for realistic bulk transactions?
- Are entry conditions based on meaningful changes?
- Does any loop perform a query, DML, or remote action per record?
- Is complex computation isolated behind a reusable, collection-based contract?
- Are execution context, sharing, CRUD, and FLS decisions explicit?
- Can Flow and Apex be versioned, reviewed, deployed, and rolled back together?
- Do tests cover branches, boundaries, permissions, bulk data, re-entry, and failures?
- Where do failed asynchronous items live, and who owns recovery?
- What metric proves the business process completed exactly once?
The best answer is rarely “always Flow” or “always Apex.” Choose the simplest architecture that remains clear under the object’s real volume and dependency load, then prove it through source control, tests, monitoring, and an owned recovery path.
If your team needs help reviewing a Flow/Apex design or simplifying crowded record automation, contact SFX Support, email founders@sfxsupport.com, or message us on WhatsApp at +91-9414849173.
Sources
- Record-Triggered Automation Decision Guide — Salesforce Architects
- Asynchronous Processing Decision Guide — Salesforce Architects
- Operational Excellence: Automation Patterns — Salesforce Well-Architected
- Manage Record-Triggered Flows — Salesforce Help
- Record-Triggered Flow Considerations — Salesforce Help
- Testing Your Flow Before Activation — Salesforce Help
- Automated Flow Testing — Salesforce Help
- Protect Your Application from CRUD/FLS Vulnerabilities — Salesforce Secure Coding Guide
- Salesforce Code Analyzer — Salesforce Developers
Still working through a Salesforce problem?
Bring the context, the error, or the interview scenario. SFX Support will help you reason through it step by step.