i n o v i
September 28, 2020 Arshad Hossain

How to Avoid Disaster with Workflows & Triggers

It’s every developer’s nightmare.

Declarative visual tools like Workflow rules, which can save valuable time by automating time-consuming business tasks such as billing or updating contact records when a company changes its name, can become dangerous when used together with code base tools like Triggers.

Blog Image

All it takes is one small error when using Workflows and Triggers together and your org can get slammed with duplicate records, overwritten data or worse — purged records that will be difficult to recover.

Most orgs rely heavily on both declarative and code base tools, and some large orgs may have thousands of them.

But many developers, administrators and end users may not be aware that when they are used together, they must be handled properly with careful coding and attention to detail in order to avoid circular references that can threaten the integrity of your data.

Sometimes, these issues aren’t immediately apparent or visible, which means the problems might not show up until the org gets larger and more complex. In other cases, the mistake can be severe and will be immediately known.

This is how to avoid disaster when using both Workflows and Triggers.

What Can Go Wrong

With some objects where records will only be created, there are often Workflow rules on that object such as a field update action. There can also be Before Triggers or After Triggers that automatically create other records when that update action occurs.

Improper coding, careless mistakes or failure to check or uncheck a single box can lead to duplicate records being created when a data manipulation language (DML) operation occurs. It can happen instantly, and might not be noticeable right away — and that makes it especially dangerous for large orgs.

For example, you may want your Account records to only be created.

To prevent updates, you would have a custom validation rule such as:


‘ISNEW()=False’, show message ‘Record is read only’

On the Account object, you have a code on After Trigger that is supposed to create a default Contact record for that account. (You have not considered ‘insert’ or ‘update’ as you expect only to insert).

You also have a Workflow rule (Old Customer), which sets the Type field value to ‘Customer Direct’ if the Source value is ‘Purchased List’

This is what the Workflow rule looks like:

Salesforce Old Customer Image

And this is what the After Trigger code would look like, creating a new contact as a default.


trigger AccountTrigger on Account (after insert, after update) {
    if(Trigger.isAfter) {
        List<Contact> listNewContacts = new List<Contact>();
        for(Account a : Trigger.new) {
            Contact c = new Contact(LastName = 'Default Contact- '+a.Name,
                          AccountId = a.Id);
        listNewContacts.add(c);
        }
        if(!listNewContacts.isEmpty()) insert listNewContacts;
    }
}

Now, with the following test code, one contact record should be created, right?


List<Account> accs = new List<Account>();
Account a = new Account(Name = 'Workflow-Trigger 201', 
              AccountSource='Purchased List');
insert a;

But, no — it creates two contact records for the account record. This is what it would look like:

Salesforce Code Image

Now, If we disable the Workflow rule, and run the same test script, it will create just one contact. Let’s take a look at that again:


List<Account> accs = new List<Account>();
Account a = new Account(Name = 'Workflow-Trigger 202', 
              AccountSource='Purchased List');
insert a;

As you can see in this output, only one contact is created:

Salesforce Code Image

Another concerning issue is that resource consumption gets almost doubled when workflow and trigger are used together.

For example, if your trigger code used 10 SOQL query statements, it will actually end up using 20 SOQL statements — which means you can only use half of the available limits. You won’t see a limit issue at the beginning, but when the org gets larger, these issues will start to show up and cause major headaches for your team.

Why That Happened

A Workflow field update action updates the record again, and it fires the before the update and again after the update Trigger code.

According to Salesforce documentation in ‘Triggers and Order of Execution’ chapter (point 12 and 13):

“If there are workflow field updates, update the record again.
If the record was updated with workflow field updates, fires before update triggers and after update triggers one more time (and only one more time), in addition to standard validations. Custom validation rules, flows, duplicate rules, processes, and escalation rules are not run again.”

How to Avoid This Mistake

To prevent this from happening, always handle IsInsert and IsUpdate context in the Trigger code. If you have to use Triggers, try to avoid using Workflow rules at the same time.

One elegant solution is to bring all the Workflow logics and action within the Trigger code itself.

Here is an example of how to do that:


trigger AccountTrigger on Account (after insert, after update) {
    List<Contact> listNewContacts = new List<Contact>();
    for(Account a : Trigger.new) {
        if(Trigger.isBefore) {
            // Workflow Rules logics
            if(a.AccountSource == 'Purchased List') {
                a.Type = 'Customer - Direct';
    }
}

if(Trigger.isAfter && Trigger.isInsert) {
    Contact c = new Contact(LastName = 'Default Contact- '+a.Name,
                  AccountId = a.Id);
    listNewContacts.add(c);
    }
  }
    if(!listNewContacts.isEmpty()) insert listNewContacts;
}

If you must use both Workflow rules and Triggers at the same time, and you want to avoid the second time run of the Trigger code due to Workflow, use a static variable to track the repeating execution of the same code. I will write another blog soon that dives into this issue in a more detailed way.

The Takeaway

Workflow rules and Trigger codes are both very important tools in the arsenal of any developer, and using them together requires special care and attention.

If you must use them at the same time, always handle IsInsert and IsUpdate context in the Trigger code, or bring all the Workflow logic and action within the Trigger code itself to avoid mistakes that can cause duplicate records or lost data or caught up limit related exceptions.

An experienced development team with specialized Salesforce training and skill sets can create code and actions that solve your business problems, while avoiding the pitfalls of accidental circular references. To learn more, email us at [email protected].