# Flow or Apex? Write the decision down before you build

> The automation argument is never really about Flow or Apex. It is about who maintains the thing in eighteen months, and whether anyone can reconstruct why it exists.

- Source: https://synconai.com/insights/flow-or-apex-write-the-decision-down
- Publisher: SynconAI (https://synconai.com)
- Desk: Platform & Architecture
- Author: SynconAI Architecture Team, Solution & technical architecture
- Published: 18 August 2026
- Reading time: 5 minutes
- Topics: Flow, Apex, Automation, Technical debt, Governance

## Key points

- The Flow-versus-Apex argument is a proxy for an ownership argument. Settle ownership first.
- One record-triggered flow per object, per timing, is the only rule that reliably survives contact with a growing team.
- Write a four-line decision record at build time. It costs minutes now and saves a forensic afternoon later.
- Bulk behaviour, error handling and recursion are where automation actually fails, not syntax choice.

---
Every org we inherit has the same archaeological layer: a stretch of automation nobody will touch. Three record-triggered flows and a trigger all fire on Opportunity. Two of them update the same field. One was built by a partner who left in 2022. Nobody can say which one owns the value, so the team works around all three.

That is not a Flow problem. It is not an Apex problem either. It is what happens when a build decision gets made in a stand-up and never written down.

## The question people argue about

The public version of this argument is about capability. Flow is declarative, so admins can maintain it. Apex is code, so it handles complexity. Both statements are true and neither helps at 4pm on a Thursday when you have to decide where the discount approval logic goes.

The capability line has also moved. Before-save record-triggered flows update fields on the same save without a second DML operation, which removed the old "Apex is faster" reflex for simple field derivation. Flow handles collections, loops, subflows, callouts via invocable actions, and fault paths. The set of things that *only* Apex can do has shrunk considerably.

So capability alone stopped being a useful tiebreaker. What did not change is everything downstream of the build.

## The question that actually decides it

Ask this instead: **who is on the hook when this breaks at month-end, and what will they be looking at?**

That single question sorts most cases within a minute.

- If the answer is *an admin, in the org, during business hours*, build it declaratively and keep it legible. An admin debugging Apex they cannot deploy to is not a support model.
- If the answer is *an engineer, from a repo, with tests*, write Apex. Do not hand someone a 40-element flow as their production surface area and call it low-code.

Notice that neither answer is about elegance. It is about who holds the pager.

## Where automation actually fails

In the incidents we get called into, the root cause is almost never the choice of tool. It is one of four things, and both tools are perfectly capable of getting all four wrong.

### 1. Bulk behaviour

A data load, a mass update from an integration, or a Data Loader run pushes 200 records into a transaction that was only ever tested with one. In Apex this is the classic query-inside-a-loop. In Flow it is a **Get Records** or an action inside a loop element, which is exactly the same mistake wearing a different hat.

The synchronous limits are unforgiving and shared across the whole transaction: 100 SOQL queries, 150 DML statements, 50,000 records returned, 10 seconds of CPU time. Your automation does not get its own budget. It shares one with every managed package, trigger and flow that runs on the same save.

:::warn Test with 200, not with 1
If the only evidence that automation works is a manual record edit, you have tested the one case that never fails in production. Load 200 records through the same path before you call it done.
:::

### 2. Error handling

Apex without a considered exception path throws and rolls back the transaction, sometimes in front of a user who has no idea what happened. Flow without a fault connector produces an unhandled-fault email to the last modifier of the flow, and a user-facing error that reads like a stack trace.

Neither is a strategy. Decide, at build time, what happens when the downstream call fails: does the record still save, does someone get a task, does it land in a retry queue, does the user see something honest?

### 3. Recursion and order

The order of execution is one document that every person building automation on the platform should have read at least once. Multiple record-triggered flows on the same object and timing do not run in a guaranteed order unless you set trigger order values, and even then you are declaring a dependency you now have to maintain.

The rule that has survived every org we have cleaned up is boring: **one record-triggered flow per object, per timing.** Before-save for field derivation on the same record. After-save for related records, callouts and notifications. Branch inside it. When it gets too big to hold in your head, that is the signal to move logic into an invocable Apex action, not to add a fourth flow.

### 4. Nobody knows why it exists

This is the expensive one. Logic gets built for a reason, a compliance requirement, a finance rule, one director's request in 2023. The reason lives in someone's head, that person leaves, and the logic becomes load-bearing folklore. It cannot be removed because nobody can prove it is safe to remove.

## The decision record

The fix is four lines, written when the decision is made, in a place the next person will find. We put it in the flow description field, the Apex class header, and the ticket.

```text
WHY:    Finance needs the discount band recalculated before approval routing.
        Source: FIN-224, approved by [role], 2026-06-02.
OWNER:  Revenue Ops admin team. Escalation: platform engineering.
CHOICE: Before-save flow. No callout, no cross-object write, admin-maintained.
RISK:   Shares the Opportunity before-save path with the territory assignment
        step. Any new before-save logic goes in that same flow, not a new one.
```

Four lines. It takes about ninety seconds. It is the difference between a future engineer deleting dead logic with confidence and a future engineer routing around it forever.

## A working rule of thumb

If you want something shorter than a framework, this is what we give teams on day one.

| Situation | Build it as |
| --- | --- |
| Set fields on the record being saved | Before-save record-triggered flow |
| Create or update related records, send notifications | After-save record-triggered flow |
| Multi-step business process with human steps | Flow, orchestrated, with fault paths |
| Complex branching finance or pricing logic | Apex service, called from Flow |
| Anything needing real unit tests and version control | Apex |
| High-volume async processing | Queueable or Batch Apex |
| Integration callout with retry and idempotency | Apex, exposed as an invocable action |

The pattern underneath it: **Flow orchestrates, Apex calculates.** Flow is where the business process lives and where an admin can see the shape of it. Apex is where the logic that needs tests, error handling and a code review lives. The invocable action is the seam between them, and it is a good seam, it keeps the process visible without pretending that a pricing engine belongs in a decision element.

## What we would change first in your org

If you inherited an org and had one afternoon, spend it here:

1. List every record-triggered flow and trigger by object and timing. Anywhere you find more than one at the same timing, you have an ordering dependency you did not choose.
2. Find every **Get Records** or action element inside a loop. Those are your next bulk incident.
3. Find every flow with no fault path on a callout or DML element.
4. Pick the three pieces of automation nobody can explain and write the WHY line for them, or get agreement to retire them.

None of that requires a rebuild, a budget, or a migration. It requires an afternoon and someone willing to write things down.

The tooling argument will keep moving, it has moved twice in the last five years and it will move again. The maintenance argument does not move. Build for the person who has to understand this without you in the room.
