Apex Map In Flow Builder
| |

Apex Map Alternative within Salesforce Flow Builder. Ultimate Step-by-step Guide

My personal story on how I created and used an alternative way of Apex Map in Flow Builder.

When I was tasked with adding 2 additional product types (custom metadata type records in Products__mdt) used in a flow, it was crucial for the flow to correctly extract these products and trigger specific logic based on their product category(a field of Product__mdt object).

I had assumed that the existing logic, developed by another developer, would continue to function seamlessly. However, this was not the case. The current flow failed to retrieve all the products from the Products__mdt object. Instead, it fetched the products individually using a “get” element for each record.

To accommodate new product types, I had to add 2 new “get” elements and adjust the existing flow’s elements. This became a repetitive process whenever new product types were introduced.

Importance of data mapping in Salesforce Flow Builder

The Products__mdt schema consists of three fields:
1. Product Name
2. Code – A unique value serving as a key
3. Category

Here’s a table representation:

Product NameProduct NameCategory
Product1ABCCategory1
Product2DEFCategory2
Product3GHICategory1
Product__mdt schema

I observed that the developer was retrieving each record from the Products__mdt individually because they were used in an iteration of a list of order items.

The Order Item includes a “Product_Code__c” field. We use the first three characters of this code, like ‘ABC‘ in ABC12390858 or ‘DEF‘ in DEF09876523, as prefixes. These prefixes correspond to the Product__mdt.Code__c field and help us find the appropriate category for each Order Item’s product code.

Their aim was to determine the category for each order item without the need to iterate through the entire order item list inside the loop. Additionally, he wanted to avoid using a “get” element within the loop. To achieve this, he obtained each element before the loop commenced and utilized them in the flow.

So, I contemplated how to create a dynamic logic flow that would accommodate new product types in Products__mdt seamlessly and dynamically. This required adjustments to the existing flow.

Why did I need to use a map in flow?

Imagine you have a map of products of keys to Categories like this:

KeyValue
ABCCategory1
DEFCategory2
GHICategory1
Key to value

You can easily look up the category for a given product key. It will be a lot of assistance, won’t it?

Finding a solution

To find a solution, I conducted research on Google to explore alternative methods for utilizing Apex Maps within flows.

Unfortunately, there were limited online resources available, especially comprehensive step-by-step guides.

I came across only two resources during my research:

  1. How to Create a Map Collection in Flows – Part 1
  2. How to Create a Map Collection in Flows – Part 2: Flow Map Methods

In other words, my disappointment with the first resource stemmed from its approach to retrieving a value by key from a map. It suggested that one had to iterate through the entire map inside another loop within Flow Builder. This method not only consumed a significant number of elements but also risked reaching the 2000-element limit quickly. Essentially, it duplicated the process of iterating through the entire list, making it impractical and inefficient.

In essence, the second resource specifically Part 2, presented a more efficient approach. While it did involve some iteration on the Apex side within the loop, it effectively prevented the excessive consumption of flow elements. This method was more resource-efficient and addressed the limitations of the first resource, making it a better option for handling the scenario effectively.

Preparing the Necessary Objects and Fields

Scenario: Maintaining data consistency in Orders with Order Items

Upon creation or updation of an Order we need to keep data consistency so we need to fetch all child Order Products (standard object OrderItem ), and we need to determine their categories. The category will be based on the OrderItem Product Code prefix(first three characters), which will be matched with the Custom Metadata Type Product table. Once the prefix of the OrderItem.Product_Code__c (e.g., ABC from ABC938573039) matches the Product__mdt.Product_Code__c field value (e.g., ABC), the corresponding category (e.g., Product__mdt.Product_Category__c, Category1) will be returned and populated in OrderItem.Category__c field.

Step 1. Creating the Product__mdt custom metadata type

– Label = Product
– Plural Label = Products
– Object Name = Product
– Description = This object will handle the assignment of product categories based on product codes.

Product custom metadata type object
Product custom metadata type object
Step 1.1. Adding fields and populating records in Product__mdt

– Label – Code -> Data Type – text -> Length – 3 -> Unique – checked
– Label – Category -> Data Type – text -> Length – 50

Product__mdt fields
Product__mdt fields

Also, Add 3 new records to Product__mdt

Product NameKeyValue
EcoCleanABCCategory1
PowerPlusDEFCategory2
LiteStreamGHICategory1
Product Records
Step 2. Creating custom fields in the Order Product (Order Item) object

Create 2 new custom fields on the Order Product(OrderItem) object named:
– Label – Category -> Data Type – text -> Length – 50
– Label – Product Code -> Data Type – text -> Length – 15

OrderItem Custom Fields
OrderItem Custom Fields

Step 3 Create a new record-triggered flow

Create a new record-triggered flow:
– Object – Order
– Trigger when – A record is updated or created

  • Add “Get Records” element to get get Order Items:

Name – getOrderItems
Object – Order Product(OrderItem)
Condition Requirements – OrderId = {!$Record.Id}
How Many Records to Store – All records

  • Add “Get Records” element to get products:

Name – getProducts
Object – Product (Product__mdt)
Condition Requirements – None
How Many Records to Store – All records

Fantastic! Let’s start crafting the Map.

Apex Map Creation

In this tutorial, we are aiming to create an Apex FlowMapClass. This class will provide the structure for our map. Within this flow, we need to generate two Apex-defined variables, both of the same FlowMapClass type. The first variable will be a simple non-collection variable, while the second one will act as a collection.

Step 1. Create the FlowMapClass Apex Class:

Firstly, let’s understand what we are creating. We’re forming a class named ‘FlowMapClass’ that includes two public variables – ‘key’ and ‘record’. Here’s how it looks:

public class FlowMapClass {

  @AuraEnabled
  public string key; //This will serve as the map key
  
  @AuraEnabled
  public SObject record; // This can represent a record from any Salesforce object
  
  public FlowMapClass(){} //This is the constructor of the class
}
FlowMapClass apex class

The ‘public string key’ is a variable that will be used as the key within the map. On the other hand, ‘public SObject record’ is a variable that will serve as the value. It can represent a record from any Salesforce object.

This process is similar to the way we construct a Apex Map like so: ‘new Map<string, Sobject>();’

For this reason, to better illustrate this, think of the ‘key’ as the unique identifier that points to a ‘record’ in the map. Each ‘key’ is associated with a ‘record’, which can be any type of Salesforce object.

By creating this Apex class, we are setting up a structure that allows us to easily manage and manipulate Salesforce records in a map format. This will enable more efficient data handling and processing in our Apex code.

Step 2. Create the CreateFlowMapClass Apex Class

The CreateFlowMapClass will be responsible for filling the map with the appropriate keys and corresponding values.

Copy/paste the following class.

public class CreateFlowMapClass {
    
    @InvocableMethod(label='Create Key to Record Map')
    public static CreateMapOutputs[] createMap(CreateMapInputs[] input){
        
        FlowMapClass[] flowMap = new FlowMapClass[]{};

        String valueKey =  keyIs(input[0],'value') ?  input[0].valueAsKey : '';//in case from flow was enabled and passed value as map key
       
        for(sObject sObj : input[0].productRecords){
            FlowMapClass obj = new FlowMapClass();
            obj.key = keyIs(input[0],'field') ? (string)sObj.get(input[0].fieldAsKey) : valueKey != '' ?  valueKey : (string)sObj.get('Id');
            obj.record = sObj;
            flowMap.add(obj);
        }

        CreateMapOutputs obj = new CreateMapOutputs();
        obj.flowMap = flowMap;
      
        CreateMapOutputs[] result = new CreateMapOutputs[]{};
        result.add(obj); 
        return result;
    } 

    private static Boolean keyIs(CreateMapInputs input, String keyIs)
    {   
        switch on keyIs {
            when 'field' 
            {
                if(input.fieldAsKey != null && input.valueAsKey == null){
                    return true;
                }
            }
            when 'value'
            {
                if(input.fieldAsKey == null && input.valueAsKey != null){
                    return true;
                }
            }
            when else 
            {
                return false;
            }
        }
        return false;
    }   

    /* Input(s) for Invocable method */
    public class CreateMapInputs{
        @InvocableVariable(label='List of Product__mdt records')
        public List<Product__mdt> productRecords;
        
        //In our example we are going to use the field as key
        @InvocableVariable(label='Use field as key')
        public string fieldAsKey;

        //You can also assign a value from flow to use as the map key
        @InvocableVariable(label='Assign Value as key')
        public string valueAsKey;
    }
    
    /* Output(s) for Invocable method */
    public class CreateMapOutputs{
        @InvocableVariable(label='Map')
        public FlowMapClass[] flowMap;
    }
}
CreateFlowMapClass apex class

The createMap method is decorated with the @InvocableMethod annotation, making it available to be called from a Flow.

It accepts an array of CreateMapInputs as input and returns an array of CreateMapOutputs.

Within this method, a list of FlowMapClass objects is created and populated with key-value pairs.

It dynamically determines the key based on the input parameters. If the fieldAsKey is provided, it uses the value of the specified field from each record as the key. If not, it uses the valueAsKey as the key. In the absence of both, it defaults to using the record’s ID as the key.

The method then returns a collection of FlowMapClass objects as an output.

CreateMapInputs Class: This inner class defines the input parameters for the createMap method. It includes:
- productRecords: A list of Product__mdt records.
fieldAsKey: A field name to be used as the key, a field of the productRecords record list passed from the flow.
valueAsKey: If you want to use another custom value passed from the flow.

CreateMapOutputs Class: This inner class defines the output structure of the createMap method. It includes a list of FlowMapClass objects that represent the map of key-value pairs.

keyIs Method: A private method within the class that checks whether to use a field as a key or a specified value as a key. It returns a Boolean value based on the provided input parameters.

In summary, the CreateFlowMapClass provides a mechanism to create a map in your Flow by dynamically populating it with key-value pairs. The key can be based on a field from your records or a specified value. This map is then used to efficiently retrieve records in subsequent Flow actions, as we will demonstrate in our example later to match product codes with categories.

Step 3. Create “ProductMap” Apex-Defined variable – non-collection

This Apex-defined variable will function as the object containing a key and record value, which will subsequently be inserted into the ProductMapCollection collection. Why is this necessary? By doing this, we create a collection that comprises key to record value pairs.

Click on New Resource and then:
– Resource Type: Variable
– API Name: ProductMap
– Data Type: Apex-Defined
– Available for output: Checked

ProductMap apex defined variable
ProductMap Apex-Defined Variable

Step 4. Create “ProductMapCollection” Apex-Defined variable – collection

Why is the Apex-Defined variable required as a collection? It’s essential because it allows us to retrieve the necessary records from the map using the key.

Click on New Resource and then:
– Resource Type: Variable
– API Name: ProductMapCollection
– Data Type: Apex-Defined
– Allow multiple values (collection): Checked
Available for input: Checked
– Available for output: Checked

ProductMapCollection Apex-Defined Variable
ProductMapCollection Apex-Defined Variable

Step 5. Populate the map using our CreateFlowMapClass Apex class

Drag and drop an action component, in the Action input “map” and select apex-CreateFlowMapClass.

select apex-CreateFlowMapClass.
Selecting apex-CreateFlowMapClass.

Then, Select “Use field as key” and type the field name you want to use as a key, yet for our example, we type “Code__c”, and then select List of Product__mdt records and search for get products and select {!getProducts}.
Expand the advanced section and type “collection” in Store Output Values and select {!ProductMapCollection}. This collection variable will be used as an input parameter later in order to get the record value based on the key.

Give a name to the action like “Create Flow Map” and click Done.

Configuring the "Create Flow Map" Action
Configuring the “Create Flow Map” Action


If you want to use a value as key, then uncheck “Use Field as Key” and check “Assign value as key” and you can prepare a value inside the flow and then pass it.
NOTE: don’t use both at the same time.

Step 6. Let’s create the GetValueFromMap apex class.

This class, as its name implies, is intended for use when we need to retrieve a record using a specific key. In this instance, the key in question is the value of the Code__c field from the Product__mdt custom metadata object.

Create the bellow apex class:

public class GetValueFromMap{
    
    @InvocableMethod(label='Get Value from a Map key')
    public static GetValueOutputs[] getValue(getValueInputs[] input	){
       
        GetValueOutputs[] result = new GetValueOutputs[]{};
        
        for(FlowMapClass loopObj : input[0].flowMap){
            if(loopObj.key == input[0].key){
                GetValueOutputs obj = new GetValueOutputs();
                obj.outputValue = loopObj.record;
                result.add(obj);
            }
        }
        return result;        
    }
    
    public class GetValueInputs{
        @InvocableVariable(label='Key' required=true)
        public string key;
        
        @InvocableVariable(label='Map' required=true)
        public FlowMapClass[] flowMap;
    }
    
    public class GetValueOutputs{
        @InvocableVariable(label='Value' required=true)
        public sObject outputValue;
    }
}
GetValueFromMap Apex Class

Step 7. It is time to test our map

Do still you recall our task!? We have to loop over order items and match their “Product Code” prefix against our map keys in order to get the corresponding record value and assign the right category to the Order Item Category field.

Before we get the record from the map, let’s create a getProductFromMap record variable of Product__mdt type.

Api Name: getProductFromMap
Data Type: Record
Object: Product__mdt
Available for output: checked

Creating getProductFromMap record variable
Creating getProductFromMap record variable

Let’s loop over “{!getOrderItems}” by adding a loop element:

Product Map Flow
Product Map Flow

Then, we create a new formula resource that will store the text prefix of OrderItem.Product_Code__c field:

Api Name: productCodePrefix
Formula: LEFT({!Loop_Order_Items.Product_Code__c},3)

First, let’s make sure the Order Item Product Code is set, then get the Product__mtd record from our Flow Map and assign the category.

Add Decision Element and check if
{!Loop_Order_Items.Product_Code__c} Is Null False. This will help us to avoid any errors while getting the record from the map.

Adding Decision Element
Adding Decision Element

Once is set, in the “Product Code Set” branch let’s get the Product from Flow Map by OrderItem.Product_Code__c prefix which is stored in the productCodePrefix formula. |

Step So, add an Action Element and choose apex-GetValueFromMap:
Fill in the following:
*Object for “Value” (Output): Product_mdt
key: {!productCodePrefix}
Map: {!ProductMapCollection}
Store Output Values: {!getProductFromMap}

Apex-GetValueFromMap action
Apex-GetValueFromMap action

Then, assign the corresponding category to the Order Item

Assigning category
Assigning category

It should look like this

Product Map Flow - LOOP logic
Product Map Flow – Loop logic

Now, we need to update order items, and for this we need a collection variable of order items.

Let’s create the Order Item Collection.
Resource Type: Variable
Api Name:
Data Type: Record
Allow Multiple Values (collection): Checked
Object: OrderItem (Order Product)

Creating Order Item Collection
Creating Order Item Collection

Let’s assign the updated Order Item to the collection
Variable: {!updateOrderItemsColletction}
Operator: Add
Value: {!Loop_Order_Items}

Assigning the updated Order Item to the collection
Assigning the updated Order Item to the collection

After the Last element of the loop add the Update element.

*How to Find Records to Update and Set Their Values: Use the IDs and all field values from a record or record collection
Record or Record Collection: {!updateOrderItemsColletction}

Updating updateOrderItemsCollection
Updating updateOrderItemsCollection

Boom! The records were updated to the corresponding Category.

This is what the hole flow looks like:

Final Flow Version
Final Product Map Flow Version

As you observed, we construct a map and fill it using the ‘Create Flow Map’ action. Then, we dynamically extract values from the Flow Map through the ‘Get Product From Map’ action.

You might argue that this process involves iterating records twice – once during map creation and again when retrieving the record from the map. While this is accurate, we are not heavily utilizing flow elements, thus avoiding reaching the flow’s limits.


The only change you need to make is to add an input parameter inside of the CreateMapInputs class (this class is inside of CreateFlowMapClass)

You are an admin and don’t know Apex? No worries!

The only change you need to make is to add an input parameter inside of the CreateMapInputs class (this class is inside of CreateFlowMapClass)

  //....................................//
   /* Input(s) for Invocable method */
      public class CreateMapInputs{
          @InvocableVariable(label='List of Product__mdt records')
          public List<Product__mdt> productRecords;
          
          //In our example we are going to use the field as key
          @InvocableVariable(label='Use field as key')
          public string fieldAsKey;
  
          //You can also assign a value from flow to use as the map key
          @InvocableVariable(label='Assign Value as key')
          public string valueAsKey;
      }
    }
CreateFlowMapClass apex class

For example you want to use map for a mapping records of a custom object, just specify the API name of the custom object.

//Add another List of Record of any object type you want
@InvocableVariable(label='List of My_Custom_Object__C records')
public List<My_Custom_Object__C> productRecords;
JavaScript

Using Apex Map alternative in Salesforce Flow Builder offers several benefits

1. Efficient Data Lookup:

Flow Maps enable efficient and direct data lookup based on keys, avoiding the need for complex and resource-intensive queries. This can significantly speed up your flow processes.

2. Resource Optimization:

By creating and using Flow Maps, you can minimize the number of Salesforce resources (like query elements or loops) required to retrieve specific records, which helps stay within resource limits.

3. Reduced Governor Limits: Flow Maps can help reduce the consumption of Flow’s governor limits, such as query limits or collection size limits, which can be crucial in complex flows.

4. Dynamic Record Retrieval:

Flow Maps allow dynamic retrieval of records, making it easy to look up records based on criteria that may change during the flow’s execution.

5. Code-Free Solution:

Flow Maps provide a code-free way to create and use maps, which is especially beneficial for administrators or declarative developers who may not have extensive coding skills. Yes, it requires code, but just copy-paste it, and just small adjustments are needed.

6. Improved Performance:

With Flow Maps, you can optimize the performance of your flows, especially when dealing with a large number of records that need to be processed efficiently.

7. Resource Reusability:

Once a Flow Map is created, it can be reused in different parts of the flow or in other flows, reducing redundancy and simplifying flow design.

8. Scalability:

Flow Maps are scalable and can handle a growing number of records, making them suitable for a wide range of use cases.

9. Dynamic Data Mapping:

Flow Maps allow you to create dynamic data mappings, where the key-value pairs can change based on the data or business logic, ensuring adaptability to changing requirements.

10. Reduced Element Counts:

By reducing the need for excessive “get” or “query” elements in your flow, Flow Maps can help keep your flow design clean and easier to manage.

11. Enhanced Maintenance:

Flow Maps can improve the maintainability of your flows since they reduce the complexity of your flow logic, making it easier to understand and update.

12. Less Code Complexity:

Instead of writing complex code to manage apex maps, Flow Maps simplify the process and allow you to accomplish map-related tasks without delving into Apex code.

In summary, Flow Maps in Flow Builder offer a user-friendly, efficient, and scalable solution for managing and accessing key-value data, contributing to improved flow performance, resource utilization, and overall productivity in Salesforce. Keep it simple.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *