SkillAgentSearch skills...

MyTriggers

Leightweight Custom Metadata driven Trigger Framework that scales to your needs. Provided with <3 by appero.com

Install / Use

/learn @appero-com/MyTriggers
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

MyTriggers

Lightweight Custom Metadata driven Trigger Framework that scales to your needs. Extended from TriggerX by Seb Wagner, provided with <3 by appero.com

ChangeLog

09-2019

  • added SObjectApiName__c to MyTrigger Settings to accomodate for sObjects not available in the sObject picklist
  • added IsByPassAllowed__c to MyTrigger Settings and a custom permission bypassMyTriggers. This allows for excluding certain users from trigger execution (e.g. Integration User).

10-2018

  • initial release

Installation

Developer Controlled Package

  • Production Instances: https://login.salesforce.com/packaging/installPackage.apexp?p0=04t1i000000gZ4HAAU
  • Sandbox Instances: https://test.salesforce.com/packaging/installPackage.apexp?p0=04t1i000000gZ4HAAU

From Source

Clone this repo

git clone https://github.com/appero-com/MyTriggers

Create a scratch org and push source

sfdx force:org:create -a MyTriggers -s -f config/project-scratch-def.json && sfdx force:source:push -r MyTriggers/framework

or deploy to your org

sfdx force:source:convert -r MyTriggers/framework -d src && sfdx force:mdapi:deploy -u <username> -d src

Resources

TriggerX by Sebastian Wagner

Issues / Known Limitations

Found something? Use the issues page

Contribution

PRs are welcome

MyTriggers HowTo

MyTriggers is a lightweight Custom Metadata driven Trigger Framework that scales to your needs. It is based upon the foundation of TriggerX that Sebastian Wagner wrote in 2013, which was a perfect starting point since it covered all one could wish for in a trigger handler except Custom Metadata Types.

The general approach behind MyTriggers is

  • run all triggers through one central handler, even across namespaces.
  • design the orchestration of logic in a way that allows you to declarative wire things differently
  • think about Triggers in new ways: configurable, closer to business needs/processes than database changes

Dreamforce 2018: "Route Your Triggers Like a Pro"

MyTriggers was released by appero GmbH and publicly presented by Christian Szandor Knapp and Daniel Stange.

If you want to recap the session, a recording will be available a few weeks after Dreamforce. The session discussion and assets will be stored at the session's tralblazer commuinty page.

You can follow Szandor on Twitter at @ch_sz_knapp, Daniel is @stangomat.

A change in perspective - a Business Process Centric Approach

When you reflect upon what your triggers actually do, you may hardly ever say that they are pieces of code that react to changes in data whenever they happen. You'd rather describe them as entry points for your business processes that, for example, create an onboarding case for new customers whenever an opportunity closes, but only for accounts that never had a closed opportunity before.

Now, with that description in mind, we should build our trigger handlers in a way that they can react to change in business requirements, and that they handle a lot more than just the records that initially started the process.

This is why MyTriggers has a records property that contains any sObject type (but all the records that are handled currently), and this is why there are Custom Metadata Type records that can be activated, grouped by names, put in a sequential orders (and re-ordered if need be).

You decide what happens when a trigger fires - the constant is that it will always open one central instance of MyTriggers that orchestrates your business processes according to your Metadata config.

General Design

  • Trigger execution will be started by instantion of MyTriggers and calling the run() method from a Trigger
  • records contains all objects currently handled by the trigger context
  • recordsNotYetProcessed can be inspected through their getter method
  • Ids of updated records can be accessed through their getter method
  • handled trigger contexts can be accessed through their setter method
  • you can enable() or disable() specific handler steps or trigger contexts at runtime
  • for each handler step, MyTriggers has to be extended
  • each handler step orchestrates its logic through overriding methods specific for the trigger contexts. _ onBeforeInsert() _ onAfterInsert() _ onBeforeUpdate() _ onAfterUpdate() _ onBeforeDelete() _ onAfterDelete() * onAfterUndelete()

Registering MyTriggers for all Trigger contexts

For MyTriggers to handle your triggers, create a Trigger for all contexts. Create a new instance of MyTriggers and call run().

Trigger AccountTrigger on Account (
	before insert,
	before update,
	before delete,
	after insert,
	after update,
	after delete,
	after undelete) {

	MyTriggers.run();

}

Building Trigger Handler Steps

Each handler step should extend the MyTriggers class and can override any of the methods.

public class newAccounts_ValidateType extends MyTriggers {


	public override void onBeforeInsert() {
		List<Account> newAccounts = (List<Account>)records;
    	}

	private void stepLogic() {
		// some method to handle the logic
	}
}

Working With the "records" Propery

MyTriggers exposes a public instance variable records that contains all records that are handled by MyTriggers. When working with the records property, you should cast it to a specific type.

(List<Account>)records

MyTriggers has a helper property for you to control your the process flow: recordsNotYetProcessed. You can access it through its getter method, Same goes for updated records - you can access their Ids through a getter

Registering Steps For Execution

The execution flow is controlled by custom metadata records of the MyTriggerSetting type. You have to specify

  • an sObject Type of a standard or custom object or a platform event
  • a class that contains your logic for this step
  • a trigger context

Additionally, you should set

  • the activation flag
  • a sequence number

Optionally, set

  • a namespace prefix for the class that you are going to call if you want to call (or build) namespaced trigger handler steps.

One Word About Sequence Numbering

It doesn't really matter which sequence numbering you choose as long as it can be sorted. To avoid renumbering a whole set of steps, choose a numbering method that allows for gaps.

Classic ERP numbering styles and sequence might make you smile - but if your initial numbering sequence was 100, 200, 300, 400, 500 ..., you can add 190 and 210 later, and 195 and 215... without renumbering the whole list if you add one step inbetween.

Deactivating Triggers

MyTriggers allows you to deactivate or re-wire Trigger steps in productive environments. But just because it is possible does not mean that it is a good idea, necessarily.

Be extra careful when you deactivate or modify productive Trigger handler steps and keep a reminder that works for you (sticky notes, an alarm clock) so that you don't forget to activate your triggers again.

Enable Steps Or Trigger Contexts at Runtime

MyTriggers allows total control over all trigger operation at runtime.

public override void onAfterInsert() {

        // records property provided by myTriggers
        List<Account> newAccounts = new Map<Id,Map>(records);

        // disable after insert trigger on Opp so it doesn't interfere
        myTriggers.disable(myCaseTrigger.class,
                           new List<System.TriggerOperation>{
                               System.TriggerOperation.AFTER_UPDATE});

        CaseService.updateCustomerCareCases(newAccounts);
    }

Finding Out If (And Which) Data Has Changed

// use string field names
List<String> fieldNamesToCheck = new String[]{'Multiplier__c','Revenue__c','OwnerId','Type__c'};

if (MyTriggers.hasChangedFields(fieldNamesToCheck,record,recordOld)){
	// logic executed when condition is true
}

// or sObjectFields
sObjectField[] fieldsToCheck = new sObjectField[]{Share__c.Multiplier__c, Share__c.Revenue__c, Share__c.OwnerId, Share__c.Type__c};

for (sObjectField field : MyTriggers.getChangedFields(fieldsToCheck,record,recordOld)){
	// process field
}

Recursion control

// add all records in the current update context
MyTriggers.addUpdatedIds(triggerOldMap.keySet());

// and use this to return only records which havent been processed before
List<Sobject> untouchedRecords = MyTriggers.getRecordsNotYetProcessed();

Documentation

MyTriggers

global virtual class MyTriggers

Leightweight Custom Metadata driven Trigger Framework that scales to your needs

: info@appero.com

Properties

records

global sObject[] records

cast records to appropriate sObjectType in implementations

Methods

  • addUpdatedIds

    add set of ids to updatedIds

  • disable

    disable disables all events for System.Type MyClass

  • disable

    disable disables all events for the trigger handler with given namespace and classname Method also works in subscriber org with hidden (public) trigger handlers from managed package

  • disable

    disable disable all specificed events for the System.Type MyClass

  • disable

    disable all specificed events for given ClassName and Namespace Also works in subscriber org with packaged public trigger handlers implementing MyTriggers

  • disable

    disable a single event for System.Type MyClass

  • disable

    disable a single event for ClassName and Namespace Also works in subscriber org with packaged public trigger handlers implementing MyTriggers

  • doConstruct

View on GitHub
GitHub Stars52
CategoryDevelopment
Updated4mo ago
Forks17

Languages

Apex

Security Score

92/100

Audited on Nov 22, 2025

No findings