Choosing Between Record‑Triggered Flows and Apex Triggers in Salesforce
A practical guide to deciding when Salesforce Record‑Triggered Flows replace Apex triggers and when they don’t, with a worked migration example, limit checks, and a checklist for safe adoption.
31 Mar 2026, 16:41 UTC

The Problem: Automation Sprawl
Most Salesforce orgs accumulate a mix of Apex triggers, Process Builder rules, and Workflow rules over the years. When the platform introduced Record‑Triggered Flows (before‑save and after‑save) the promise was a single declarative surface that could replace the majority of trigger logic. The reality is messier: some use cases migrate cleanly, others hit governor‑limit walls or debugging gaps that only Apex can bridge. This post walks through a practical decision framework, a worked migration example, and the trade‑offs you should verify before standardizing on Flow.
When Flow Wins
- Before‑save field updates on the triggering record – Flow executes in the same transaction as a before‑insert/before‑update trigger but does not consume SOQL or DML limits for the $Record assignments. In a bulk insert of 200 Accounts, a before‑save Flow that sets
Account.Ratingbased onAnnualRevenuetypically shows 1 SOQL query in the Debug Log, whereas an equivalent Apex trigger often shows 1‑2 queries plus the DML row count. - Related‑record creation or email alerts after commit – After‑save Flows run in a separate transaction (like after triggers) and support DML on other objects, email actions, and scheduled paths for async work.
- Team skilling and bus‑factor reduction – Admins and low‑code developers can maintain Flows without a dedicated Apex team, provided the logic stays within Flow’s element limits.
When Apex Still Wins
- Bulk‑heavy logic with complex maps – Apex shares the org‑wide 100 SOQL / 150 DML limits per transaction, while each Flow interview is capped at 2 SOQL queries (before‑save) and 3,000 executed elements (Winter ’25). A trigger that aggregates 10,000 child records into a parent roll‑up will usually stay under Apex limits but exceed Flow’s element ceiling.
- Callouts and external integrations – Pure Flow cannot make HTTP callouts. You must wrap the callout in an
@InvocableMethodApex class or an External Service registration, adding a deployment artifact anyway. - IDE‑grade debugging – Apex offers breakpoints, stack traces, and the Apex Replay Debugger in VS Code. Flow debugging is limited to element‑by‑element logs under the Automated Process user, which can be noisy for high‑volume runs.
- Mandatory test coverage – Deploying Apex enforces 75% code coverage, catching regressions early. Flow deployments have no enforced coverage; you must invest in FlowTestCoverage or a manual regression suite.
Worked Example: Migrating a Simple After‑Insert Trigger
Assume an existing trigger on Opportunity that creates a Task for the owner when StageName becomes Closed Won.
- Inventory the trigger – Verify it only does a single DML insert on
Taskand has no recursion guards or callouts. - Run the Trigger‑to‑Flow Migration Tool (Beta) – In Setup → Flow Migration, select the trigger. The tool generates an after‑save Record‑Triggered Flow with an entry criteria of
ISCHANGED(StageName) && ISPICKVAL(StageName, 'Closed Won')and a Create Records element forTask. - Add recursion protection – The generated Flow lacks a
$Record.__Prior__check. Insert a Decision element before Create Records:$Record.StageName <> $Record.__Prior__.StageName. This prevents re‑entry when the Task creation triggers a workflow that updates the Opportunity again. - Deploy and validate – Deploy the Flow as a new version (metadata type
Flow). Activate it, then deactivate the Apex trigger. Use Data Loader (CLI or UI) to insert 200 Opportunities withStageName = 'Closed Won'.
Required permission: API Enabled and Modify All Data on the target object. Expected check: In Setup → Debug Logs (filter on Automated Process), each Flow interview should show Flow Elements Executed ≤ 3,000 and 0 SOQL queries for the before‑save portion (none here) and 1 DML for the Task creation. Risk: If the org’s daily Flow interview limit (250 k/24 h) is near capacity, the bulk load may throttle; monitor System Overview → Flow Interviews (24h) before the run.# Data Loader CLI example (run from a machine with API access) java -cp dataloader-58.0.0-uber.jar com.salesforce.dataloader.process.ProcessRunner \ process.name=insertOpportunities \ process.operation=insert \ process.mappingFile=opportunity-map.sdl \ dataAccess.name=csvRead \ dataAccess.type=csvRead \ dataAccess.csvFile=opportunities.csv - Rollback plan – Because the change is a metadata activation, rollback is simply reactivating the Apex trigger and deactivating the Flow version. No data mutation occurs beyond the Tasks already created.
Trade‑offs and Limits to Verify
| Dimension | Flow | Apex |
|---|---|---|
| Element limit per interview | 3,000 (Winter ’25) | N/A (governed by DML rows) |
| SOQL per interview (before‑save) | 2 | 100 per transaction |
| Daily interview quota | 250,000 per org | N/A |
| Callout support | Via Invocable Apex / External Service | Native |
| Debugging fidelity | Element logs only | Breakpoints, replay debugger |
| Deployment safety net | No enforced test coverage | 75% coverage required |
Practical verification steps:
- Enable a Debug Log for the Automated Process user, run a representative Flow, and confirm the
FLOW_ELEMENT_LIMIT_USAGEevent stays well below 3,000. - Check Setup → System Overview → Flow Interviews (24h) before moving high‑volume objects (e.g., Case, Lead) to after‑save Flows with scheduled paths.
- For any Flow that calls an invocable Apex action, add a
System.debug('Callout executed')and verify the callout appears in a separate async log withSTATUS=Success.
Actionable Closing Checklist
- Catalog every active trigger and classify its logic: field‑only updates, related‑record DML, callouts, complex bulk maps.
- Run the Migration Tool on all “field‑only” and “simple related‑record” triggers. Review each generated Flow for missing recursion guards.
- For each candidate Flow, execute a bulk test (Data Loader or Anonymous Apex) and capture Debug Logs. Confirm element count and SOQL usage stay within limits.
- Document any trigger that requires callouts, exceeds 3,000 elements, or needs IDE debugging – keep those in Apex (or move to a service‑layer Apex class invoked from Flow).
- Establish a regression suite: either FlowTestCoverage (if you have the license) or a scheduled Apex test that re‑runs the critical Flows nightly.
- Monitor the org’s Flow interview quota weekly; set an alert at 80% utilization to avoid silent throttling.
By treating Flow as the default for declarative, record‑centric automation and reserving Apex for the proven edge cases, you reduce technical debt while keeping the safety nets that large‑scale Salesforce deployments demand.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.