Triggers are where Salesforce orgs quietly rot. Logic gets stuffed into the trigger body, a second trigger appears on the same object, execution order becomes a coin flip, and one day a data load fires automation it was never supposed to. A trigger framework fixes all of this by giving every trigger the same predictable, controllable shape.

The problem with "just put it in the trigger"

Apex triggers are easy to write badly. The three failure modes show up in almost every legacy org:

  • Multiple triggers per object. Salesforce doesn't guarantee the order they run in, so behavior depends on metadata luck.
  • Logic in the trigger body. It's hard to unit-test, impossible to reuse, and mixes context-detection with business rules.
  • No off switch. When a migration needs to skip automation, someone comments out code and redeploys — in production.

The community consensus, for years, has been "one trigger per object that delegates to a handler class." A framework simply formalizes that so every developer does it the same way.

The core idea: dispatcher + handler

Your trigger stays exactly one line. It hands control to a dispatcher, which figures out the context and calls the right method on your handler:

trigger AccountTrigger on Account (before insert, before update,
        after insert, after update, before delete, after delete, after undelete) {
    MF_TriggerDispatcher.run(new AccountTriggerHandler());
}

The handler extends a base class and overrides only the contexts it cares about. Everything is a normal, mockable Apex method:

public class AccountTriggerHandler extends MF_TriggerHandler {
    public override void beforeInsert(List<SObject> newList) {
        for (Account a : (List<Account>) newList) {
            if (String.isBlank(a.ShippingCountry)) {
                a.ShippingCountry = 'USA';
            }
        }
    }

    public override void afterUpdate(Map<Id, SObject> newMap, Map<Id, SObject> oldMap) {
        // recalculate, roll up, enqueue async work, etc.
    }
}

Because the handler is a plain class, you can instantiate it in a test, call afterUpdate directly, and assert on the result — no DML gymnastics required to reach a branch.

Bypass: the feature you didn't know you needed

The single most valuable capability of a good framework is the ability to turn automation off on purpose. Data loads, migrations, and integration users frequently need to skip trigger logic — either for performance or to avoid firing side effects. A framework driven by Custom Metadata lets you do this as configuration:

  • Globally — pause all handlers during a maintenance window.
  • Per object — bypass just Account while you reload it.
  • Per custom permission — assign a "Bypass Triggers" permission to your integration user so its inserts skip handler logic, while everyone else's don't.
// Skip a handler for a scoped block of code
MF_TriggerHandler.bypass('AccountTriggerHandler');
insert biglistOfAccounts;
MF_TriggerHandler.clearBypass('AccountTriggerHandler');

No code edits, no redeploys, no risk of forgetting to uncomment something.

Recursion control

The other classic trigger bug is infinite re-entry: your afterUpdate updates a related record, which fires another trigger, which updates back, and so on. A framework carries a recursion guard — a static set that records which handler/context pairs have already run in the current transaction — so the second entry is a no-op. You get correctness without sprinkling Boolean alreadyRan flags across your codebase.

Rule of thumb: triggers detect context, handlers hold logic, and services hold reusable business rules. Keep each layer thin and the whole org stays testable.

What "lightweight" should mean

Frameworks can over-engineer this into dozens of interfaces. You don't need that. The MF Trigger Framework is deliberately small: an interface, a virtual base handler with per-context methods, a dispatcher, a Custom Metadata Type for bypass configuration, and a custom permission. That's enough to standardize every trigger in the org and nothing more.

Frequently asked questions

Why only one trigger per object?

Salesforce doesn't guarantee the order multiple triggers run in on the same object, so behavior becomes unpredictable. One trigger delegating to a handler gives a single deterministic entry point.

How do you bypass a trigger during a data load?

A framework exposes a bypass switch — globally, per object, or gated by a custom permission — so integration users or migrations skip handler logic without editing and redeploying code.

How do you prevent recursive triggers?

A recursion guard uses a static variable to track whether a handler/context already ran in the current transaction, so an update made inside a handler doesn't re-enter the same logic infinitely.