Apex triggers or Flows: deciding by who maintains it

Both can do the job. Only one of them can be changed by the person who will be here when it breaks.

Apex triggers and Flows overlap heavily, and the capability comparison is the least useful way to choose between them. The decision that matters is who maintains the automation after the person who built it has moved on, plus a second consideration nobody plans for: when both fire on the same object, the interaction is hard to reason about and harder to debug. This gives concrete criteria, the ordering problem, and a rule for mixed estates.

Ask who will change this in two years

The capability question resolves quickly and unhelpfully: Flow handles most of what most orgs need, and Apex handles everything. That comparison points at Apex and it is the wrong conclusion.

The better question is who will be sitting in front of this the day a business rule changes. In most mid-market organisations that person is an administrator, not a developer, and they can change a Flow and cannot change a trigger.

So the default is Flow, and the burden of proof sits with Apex. That is not a judgement about engineering quality. It is an observation about staffing: automation nobody on staff can safely modify becomes automation nobody modifies, and then it becomes a workaround.

The counter-case is real though, and it is not about elegance. Apex is the right answer when the logic needs to be tested automatically, when it has to run inside a transaction with strict error handling, or when the volume makes Flow's per-record behaviour a limits problem.

The criteria, stated concretely

The criteria, stated concretely
ConsiderationFlowApex trigger
Who can modify itAdministratorDeveloper only
Automated testsLimitedFull test framework
Behaviour under bulk loadHarder to controlControllable if written bulk-safe
Version control and diffingLarge generated fileReadable source
Error handlingCoarseExplicit
External calloutsVia an Apex actionNative
Time to build a simple ruleMinutesHours, including tests

Five criteria settle almost every case. Apply them in order and stop at the first that decides.

Volume. Does this run across large record sets, for example during a data load? Salesforce's execution governors and limits apply to both, and Flow's behaviour under bulk is less controllable than well-written Apex. Above serious volume, Apex.

Testability. Does this logic need an automated regression test? Apex has a test framework and Flow largely does not, which means Flow logic is verified by a person clicking, once. If a mistake here has financial consequences, that is not sufficient.

Error handling. What should happen when a downstream call fails? Apex gives you explicit control over the transaction and the failure path. Flow's error handling is coarser and harder to make deterministic.

External calls. Anything involving a callout to another system needs Apex's control over callout limits and timeouts.

Everything else. Flow.

The interaction problem nobody plans for

In practice you will not choose one. You will inherit an org with both, on the same object, built by different people in different years.

That is where the genuinely hard bugs live. When a trigger and a Flow both act on Opportunity, the execution order across the save is defined by the platform rather than by either author, and neither author had the other in mind.

The symptom is recognisable: a field is set correctly, and by the time the record is saved it holds a different value, and no single piece of automation explains it. Debugging that means reading everything that touches the object, which is only possible if somebody wrote down what touches the object.

The worse variant is recursion. Automation updates a record, the update re-fires automation, and the transaction either loops or hits a limit. Apex gives you a static guard against re-entry. Flow gives you fewer options, which is why heavily automated objects tend to end up in Apex regardless of the original preference.

The mitigating rule is boring and it works. One object, one trigger, one Flow at most. If a second is needed, extend the existing one instead of adding another. That single constraint removes most of this category.

Bulk safety applies to both

Whichever you pick, the automation has to survive somebody loading 200,000 records at once, because sooner or later somebody will.

For Apex the rules are documented and mechanical. Salesforce's guidance on bulk triggers is that a trigger receives a collection and must be written that way: no query inside a loop, no DML inside a loop, one query and one DML per transaction.

For Flow, the equivalent discipline is to be deliberate about record-triggered configuration and to keep per-record work minimal. A Flow that performs a lookup and an update per record behaves acceptably on a hundred records and badly on a hundred thousand.

The test that catches this does not exist by default. Add it deliberately: a test that inserts or updates 200 records at once. A suite that only ever exercises single records is proving that the code compiles, and it will pass right up until the load runs.

This is the same failure class as everything else in the governor limits that bite in production, and it shows up on the same days: month end, quarter close, and the morning after a marketing list arrives. Anything that will be exercised by a large data load should be volume-tested before the load, not during it.

Execution context, and the security consequence

There is a difference between the two that is easy to miss and expensive to miss.

Apex runs in system context by default, meaning it ignores the current user's object permissions, field permissions and record sharing unless the code opts into enforcing them. Salesforce documents how to enforce sharing rules explicitly, which tells you that not enforcing them is the default.

That default is convenient and it is a genuine exposure. Automation written without thinking about context can read or write records the invoking user could never see, and nothing in the interface signals it.

Flow has its own context settings with the same class of consequence. Whichever tool you use, the question has to be answered deliberately: does this automation act as the user, or as the system, and is that the intended answer?

Salesforce's security overview is the reference for the surrounding model, and the record-level layer that automation can bypass is covered in who can see which record, and why.

What to do with an org that already has both

Do not start with a rewrite. Start with an inventory, because you cannot make a sensible decision about automation you have not enumerated.

For each object, list every trigger, every record-triggered Flow, every process built on older automation tools, and every validation rule. Note what each one does in one sentence and who owns it. This takes a few days and it is the artifact everything else depends on.

Then consolidate rather than convert. Converting a Flow to Apex because Apex is better is work with no business outcome. Consolidating three Flows on the same object into one, so the ordering becomes explicit, is work with a direct one.

Retire the genuinely obsolete first. Every mature org carries automation implementing a rule that changed years ago, still firing, still consuming limits. That is the cheapest win available and it reduces the surface before you touch anything else.

Salesforce's data model documentation is worth having open during the inventory, because a surprising amount of what automation does is compensating for a field that means two things.

Whatever you build, make it reviewable

Both tools produce metadata, both belong in version control, and only one of them produces a diff a human can read.

Apex source diffs cleanly. Flow definitions are large generated files where a small logical change can produce a large textual one, which makes review harder rather than impossible. The response is to keep individual Flows small and single-purpose, so the diff at least has a bounded scope.

Review both anyway. Atlassian's summary of what code reviews are and why they save time applies with more force in Salesforce than in most stacks, because so much of what ships is configuration with no automated test behind it. A second pair of eyes is frequently the only control there is.

Enforce it with repository policy rather than with good intentions. Microsoft's documentation on branch policies covers required reviewers, and the pipeline that carries this is the subject of change sets or source-driven deployment.

Somebody also has to own the standard, and in most mid-market organisations the choice is currently made per feature by whoever built it. Setting the rule once, writing it down, and enforcing it at review is the sort of bounded delivery work a fractional technical project manager is brought in for, and it decomposes the same way 159 tracked work items with acceptance criteria made a clinical research program fundable: one item per object, one inventory line each.

One last habit worth adopting. Every trigger and every Flow carries a one-line description saying what business rule it implements and who asked for it. It costs seconds at build time and it is the difference between a two-hour investigation and a two-minute one, three years later, when the person reading it is not you.

Start a conversationMore insights