Background
Over the past couple years, I’ve repeatedly found myself using custom metadata types to control business logic, whether it be implemented in Apex or Flow. The first example where this design came in especially handy was in controlling 25+ regional variations across an otherwise common global Salesforce Field Service implementation. Most recently it has been utilized on core platform and CPQ projects, and it continues to prove a great way to control behavior without changing Apex or Flows themselves. Let’s look at a very simple example of how this works.
Scenario
The Order object has a Status field that includes ‘Draft’ and ‘Activated’ picklist values. Our custom implementation necessitates the use of a ‘Canceled’ and ‘Ordered’ status as well. We want to only allow a certain progression through the statuses – for example Draft => Activated => Ordered. From any of these statuses we will allow Canceled to be selected. Additionally, we have dependent behaviors that should be triggered based on the status selected, such as firing off a Review process (“Review Required’), sending a Notification (“Notification”) and Syncing to an ERP (“Sync”). Most importantly, we want to enforce these behaviors regardless of whether they’re coming through Apex or Flow.
Custom Metadata Type Setup
Instead of writing out the logic in Apex of Flows directly (whether in the Flow or in invocable method calling to same Apex logic), we will set up and refer back to a Custom Metadata type to drive behavior.
First, we will create a Custom Metadata Type called ‘OrderStatus__mdt’. Then we create the following custom fields:
Field | Type |
Order Status | Picklist (‘Draft’Activated’,’Ordered’,’Canceled’) |
Allowed Previous Steps | Text |
Review Required | Checkbox |
Notification | Checkbox |
Sync | Checkbox |
Next we’ll create 4 records, one for each of our 4 Statuses. In ‘Allow Previous Steps’ we declare which Statuses can transition into each subsequent Status. We additionally say whether a given step should trigger ‘Review Required’, ‘Notification’ or ‘Sync’. We won’t actually build out those behaviors, but this is to demonstrate the type of control we can put in the records.
Apex Example
There are many ways to use the Custom Metadata records now, but we will employ a very simple statusManager method that is called any time the Order Status changes. We won’t show all the preceding logic but rather we’ll pass in a map of all the OrderStatus__mdt records as well as the new and old Orders.
To build the map, see the Help article for getAll() records.
mapStatus = new Map<String,OrderStatus__mdt>();
Map<String,OrderStatus__mdt> mapStatusAll = OrderStatus__mdt.getAll();
for(OrderStatus__mdt myStatus : mapStatusAll.values()){
mapStatus.put(myStatus.OrderStatus__c,myStatus);
}
The statusManager method can be used to handle all sorts of logic, but we’ll do an example here where we check old vs. new status.
//Method to control status changes on Order. Pass in Map of all OrderStatus__mdt records.
private void statusManager(Order newOrder, Order oldOrder, Map<String,OrderStatus__mdt> mapStatus){
//Example section to compare status change to allowed status changes.
if(!mapStatus.get(newOrder.Status).AllowedPreviousSteps__c.contains(mapStatus.get(oldOrder.Status).OrderStatus__c)){
newOrder.Status.addError('Changing Status from '+oldOrder.Status+' to '+newOrder.Status+' is not allowed.');
}
}
If we now try to move Order status from ‘Draft’ straight to ‘Ordered’ we can see that the change is caught as invalid. Again, this is just a simple example, but we could also use this to trigger a callout to an ERP (if new status is ‘Sync’ = true), create a Notification (if new status is ‘Notification’ = true), etc.
Flow Example
Importantly, the same approach can be used to govern behavior in Flows, thus ensuring that we share business logic no matter what tool is employed. Let’s look at a simple example where we prevent an incorrect Status from being set to begin with. To do this, we’ll create a simple flow for ‘Order Status Update’ where we get the current Order Status, and then fetch the available next Statuses based on the custom metadata records where ‘AllowedPreviousSteps__c’ includes the current Status.
Note that this isn’t meant to be a Flow tutorial so I won’t go into great detail on these steps. First, we set up the input variable of Order object. Then we create an Input screen with a Record Choice Set. That Record Choice Set will query the Custom Metadata Type OrderStatus__mdt for records where ‘AllowedPreviousSteps__c’ is contains the current Order Status.
Based on the choice made from the choice set, we’ll assign that OrderStatus__c value to the current Order status and update the Order record. Exposing the Flow and passing in the current record, we now have a tool for updating the Status where we can only select possible values. Again, this is a very simple use case, but following this approach we can accomplish any number of complex configuration storing our business logic only in Custom Metadata.
When in ‘Draft’, we see only ‘Activated’ and ‘Canceled’.
When in ‘Activated’, we see only ‘Ordered’ and ‘Canceled’. And on and on.
Wrapping Up
This approach is one that has really paid off over the past couple of years across a variety of use cases. It involves a bit more thought and effort up front, but really pays off especially in situations where the solution may be somewhat (or very) dynamic going forward. Little is more satisfying than updating a few fields on a record to powerfully change otherwise complex business logic (without touching Apex, Flows, etc.). Hopefully this simple Status Manager example gives you an idea of the kinds of things we can accomplish on a full and complex implementation by utilizing this technique.
Thanks for reading, and let me know of your success stories using this approach!