Posts

Process approvals using a screen flow in Slack

With the Spring 23 release, screen flows in Slack will be generally available. I set out to explore one use case.

Approvals in Slack are currently possible but have some limitations

  1. It is only available with the Salesforce for Slack app but not available if you setup one of the cloud specific apps (Sales cloud for Slack, Service cloud for Slack etc.)
  2. You can only approve or reject but cannot add an approval comment.

I wanted to see if I can improve on that by using a screen flow in Slack. This blog will not cover the initial setup and connection of Salesforce to Slack. You can find details on how to do this in the official documentation.

My solution was inspired by two blogs

  1. Alex Edelstein created an invocable action for processing approval requests that includes sending an approval comment
  2. Yumi Ibrahimzade demonstrated how to copy an approval comment from the approval history related list to a field on the record

In order to send a screen flow to Slack you have to create two separate flows that will work together.

  1. A screen flow – this is what will surface to the user in Slack and process the user inputs there. This flow has to be marked as available in Slack. Marking a flow as ‘Available in Slack’ generates a separate ‘launcher’ invocable action that can be used in other flows
  2. A record triggered flow – This flow will act as a dispatcher and will use the launcher action, we created in step 1, to send the screen flow to Slack when record conditions are met. Slack actions can only be processed asynchronously, after the record is saved.

To test this, I setup a simple one step approval process, with simple actions that update an approval status picklist field on the record

I then created my screen flow which has only one screen. The screen displays an approval request to the user, along with some record details, and captures the responses. The flow will then use those responses to process the approval using the invocable action, and update the record with the approval comment. The flow has some input variables that will get their value from the dispatching flow. I marked this screen flow as available in Slack. I was not successful in passing a record link to the screen flow. Slack uses a special markup language to format links, which works well when sending a Slack message using the “Send Slack Message” core flow action, but it did not work when sending in the screen flow action.

The next step was to create the record triggered flow that will send to Slack. My trigger was based on a custom Approval status field set to submitted, when a user will submit the record for approval. This will work well for a single step approval process but if you have a multi step process, additional conditions may be needed to determine the current approval step. This flow gets the most recent approval process instance, work item, and approver userId for the triggering record and passes this information, along with the record Id, to the screen flow action.

When both flows are combined, I was able to send an approval request to Slack, process the response, and update the record with the most recent approval comment. Here is a short video of the complete process.

This is a simple use case for a single step approval process with a single approver. It can be enhanced to handle more complex approval processes.

  1. If there are multiple approvers, the dispatching flow will need to get a collection of all the work items and loop through to send the screen flow to all of them.
  2. If a queue is set as the approver, the the dispatching flow will need to get a collection of all members of the queue to find their userId 
  3. The screen flow can be updated to add a recall action

Invoke a MuleSoft RPA Process from Flow using External Services

MuleSoft Robotic Process Automation (RPA) gives you the power to automate business processes that usually require human input and interact with systems that don’t have an API, extending the reach of Salesforce’s process automation capabilities by integrating these RPA processes into flows in Salesforce as of Winter ’23!

Use MuleSoft RPA Manager to publish REST APIs for your RPA processes to Anypoint Exchange, which you can then register as External Services in Salesforce and invoke from low-code tools like Flow Builder.

This post describes the configuration steps necessary to invoke a MuleSoft RPA Process in Flow Builder. It assumes familiarity with MuleSoft RPA, Named Credentials and External Credentials, Permission Sets, External Services, and Flow Builder.

Step 0: MuleSoft RPA Pre-requisites

  • Configure the connection between MuleSoft Anypoint platform and your org to import MuleSoft RPA APIs.
    • In MuleSoft RPA Manager, publish the RPA process as a REST API to Anypoint Exchange.
    • In MuleSoft RPA Manager, copy the API key from the User Management | User API Keys page.

MuleSoft RPA Manager: User Management page

Steps 1-3: Connect Salesforce to MuleSoft Anypoint Platform

These steps create the initial connection between MuleSoft Anypoint platform and your org:

  • Step 1: Create a Connected App in MuleSoft Anypoint PlatformA connected app in MuleSoft Anypoint platform allows Salesforce to call MuleSoft APIs.
  • Step 2: Create an Authentication ProviderUse the ID and the secret from your MuleSoft Anypoint Platform connected app to create an authentication provider.
  • Step 2b: Update Your MuleSoft Anypoint Platform Connected App – Use the Salesforce authentication provider callback URL to update your MuleSoft Anypoint Platform connected app.
  • Step 3: Create a Legacy Named Credential (Retrieve APIs) – Create a legacy named credential to access/authenticate into your MuleSoft Anypoint Platform connected app from Salesforce and retrieve the APIs published to MuleSoft Anypoint Exchange. The legacy named credential stores the URL for MuleSoft Anypoint Platform. External Services uses the legacy named credential when listing the MuleSoft APIs available for import.

Note: the ability to create a ‘Legacy’ named credential is still supported, although it will be discontinued in a future release, at which point, this portion of the instructions will be updated to reflect the new capability.

Salesforce: Named Credentials Home Page

Step 4: Create a Named Credential (Runtime) and External Credential

Note: MuleSoft RPA authenticates clients invoking RPA processes through API keys. The Salesforce Winter ’23 release includes new functionality in Named Credentials and External Credentials that supports this type of authentication.

  • Create an external credential, permission set mapping and custom header
    • Overview
      • Create a second named credential that stores the endpoint for MuleSoft RPA Manager. External Services uses the second named credential when invoking “runtime” the MuleSoft RPA process in a flow.
    • External credential:
      • Before creating a second named credential, first create an external credential.
        • Create an external credential to capture the API key and other authentication details.
        • Then create a named credential to store the actual endpoint.
        • This allows for multiple endpoints to be addressed with the same authentication configuration.
      • After entering the details for the external credential, click Save. You’re taken to the Named Credentials screen. Now you need to create a permission set mapping for this new external credential. This mapping ensures that only the correct users get access to these credentials.
    • Permission set mappings:
      • Click External Credential and select the external credential you created.
      • Scroll to Permission Sets Mappings so you can link the external credential to a user’s permission set.
      • Click New to create a permission set mapping for this external credential using the details below for an external credential that uses ‘Custom’ as the authentication protocol.
    • Custom headers:
      • Lastly, create a custom header for this external credential using the the details below as guidance for how to create a custom header.

Example
Salesforce: External Credentials Detail Page

  • Create a named credential
    • Now that you have an external credential, create a named credential and then link it to the external credential you just created in the previous step. As mentioned before, this second named credential stores the endpoint for MuleSoft RPA Manager. External Services uses the second named credential when invoking “runtime” the MuleSoft RPA process in a flow. Use the details below for how to create a named credential.

Example

Salesforce: New Named Credential Window

  • Verify that the external credential and the named credential you just created are linked
    • From the Named Credentials page, click External Credentials.
    • Click the name of the external credential from this list and confirm that the named credential you just created appears in the Related Named Credentials area shown in the example below.
    • As a reminder:
      • The Named Credential stores the MuleSoft RPA Endpoint
      • The External Credential stores the MuleSoft RPA API Key + Authentication details

Example
Salesforce: External Credentials Detail Page

Step 5: Register the MuleSoft RPA API as an External Service

As you are configuring the external service, recall that you created two named credentials.

  • Legacy named credential (Retrieve APIs) – this named credential stores the MuleSoft Anypoint platform URL and is used to retrieve the APIs published to MuleSoft Anypoint Exchange. Use this named credential in the “Select a MuleSoft Anypoint Platform Account” screen.

Example

  • Named credential (Runtime) – this named credential stores the MuleSoft RPA Manager URL and is used when invoking “runtime” the MuleSoft RPA process in a flow. Use this named credential in the “Configure your MuleSoft Anypoint Platform Service” screen.

Example

Example

Step 6: Invoke a MuleSoft RPA Process in a Flow

  • Now you are ready to start and check the status of the MuleSoft RPA process from a flow.
  • If you follow the steps outlined in the help docs Invoke a MuleSoft RPA Process in a Flow, listed below are a few additional tips to help guide you through the flow configuration.

Create Resource for RPA Process Input Variables

Tip: In step 2, when creating a New Resource to store the input variables for the RPA process, you can obtain the External Service Name and Apex Class Name from the External Services Detail Page to help configure the New Resource

Salesforce: External Services Detail Page

Assign Values to RPA Process Input Variables

Tip: In step 3, you can obtain the information about each variable/input parameter that needs to be defined in the above Apex class from the External Services Detail Page as well as the OpenAPI specification (a few examples shown below).

RPA Process OpenAPI Specification

OperationId: startProcess > ProcessExecutionWithExecutionId

ProcessExecutionWithExecutionId > ProcessExecution

ProcessExecution > inputArguments

Example Custom Flow Actions for Omni Supervisor

In the Winter ’23 release, an exciting new feature was shipped allowing admins to put more power in the hands of Supervisors to run their teams’ with a new configurable action framework from Omni Supervisor. I’ve gone into some details on creating your own actions in this post, but this page is here to describe a package of custom actions that I’ve created that you can install and use make use of as you see fit.

You can install these Supervisor Flow Actions as a standalone package from this link (unmanaged)

  • Ensure that Omni Channel is enabled before installing

Once installed – check for each flow below as to whether you need to make updates, before adding it to a Supervisor Configuration (as detailed in the help docs) – all Flows that reassign work will need updating.

Flow list:


Password Reset

Description:
This flow is designed to be used on the ‘Agent Summary’ tab of Omni Supervisor, and expects a list of User record Ids to be passed in (to the input variable ‘ids’ – this is done by default from Omni Supervisor). This will show the above screen – where you can choose to send a notification to the affected Users – choosing to send the notification will result in the users getting a Salesforce notification (bell icon), informing them that their password has been reset. They will also get the standard password reset email (whether you select the checkbox or not).

Note – this Flow is set to ‘Run as System’ as it is calling Apex, and the ‘system.resetPassword()’ function.

Required Updates

  • Add this action to the appropriate ‘Supervisor Configurations’ (or create one) that you want this to appear for.
  • Ensure you have a ‘Custom Notification’ in the Setup screens which is active, and applies for both Desktop and Mobile (for the checkbox to send a notification to work)
  • Ensure the user has access to the ‘ResetUserPassword’ Apex Class (via Permission set or Profile)

Optional Updates
None


Update Capacity

Description:
This flow is designed to be used on the ‘Agent Summary’ tab of Omni-Supervisor, and expects a list of User record Ids to be passed in (to the input variable ‘ids’ – this is done by default from Omni-Supervisor). This will show the above screen – where you can enter the new capacity to use for the selected user(s). Note that in doing this, it may create a new ‘Presence Configuration’ in the back end – this is why this Flow needs to be ‘Run as System’, as a standard user won’t have permission to create or edit those admin records.

If a Presence Configuration with the entered Capacity doesn’t exist, then the Flow will clone the ‘default_presence_configuration’ (created when you turn on Omni-Channel) to create a new one, using the capacity entered – therefore you may end up with a lot of different Presence Configurations when using this Flow. Therefore, please update the default presence configuration to have the values you expect/want for each of these new Presence Configurations.

Choosing to send the notification will result in the users getting a Salesforce notification (bell icon), informing them that their capacity has been updated, and they need to log out and back in to Omni-Channel.

Required Updates

  • Add this action to the appropriate ‘Supervisor Configurations’ (or create one) that you want this to appear for
  • Ensure you have a ‘Custom Notification’ in the Setup screens which is active, and applies for both Desktop and Mobile (for the checkbox to send a notification to work)
  • Update the ‘default_presence_configuration’ to have any settings you want replicated across new Presence Configurations

Optional Updates

  • You can change this to create a Presence Configuration per agent – this creates a lot of overhead, but means that their capacity change can take effect right away (i.e. without logging in & out of Omni). The risk with this is updating the capacity of an existing Presence Configuration, will result in all assigned users having the new capacity, which may not be what you expect or want.

Send Notification

Description:
This flow is designed to be used on the ‘Agent Summary’ tab of Omni-Supervisor, and expects a list of User record Ids to be passed in (to the input variable ‘ids’ – this is done by default from Omni-Supervisor). This will show the above screen – where you can enter the subject and body of the notification you want to send the selected User(s).

Sending the notification will result in the users getting a Salesforce notification (bell icon), with the entered values. Clicking on this notification will take them to their ‘Home’ screen

Required Updates

  • Add this action to the appropriate ‘Supervisor Configurations’ (or create one) that you want this to appear for.
  • Ensure you have a ‘Custom Notification’ in the Setup screens which is active, and applies for both Desktop and Mobile (for the checkbox to send a notification to work)

Optional Updates

  • You can copy/clone this Flow to send a message in your appropriate messaging platform (e.g. Slack, Teams or SMS) or to send an Email, using the various Flow actions available (either OOB, or via install).
  • You can update the navigation from the ‘Send Navigation’ action to go to a different location as appropriate for you (but a navigation target of some sort must be defined).

Reassign All

Description:
This flow is designed to be used on the ‘Queues Backlog’ tab of Omni-Supervisor, and expects a list of Queue (Group) record Ids to be passed in (to the input variable ‘ids’ – this is done by default from Omni-Supervisor). This will show the above screen – where you can select the appropriate Queue or User you want to assign all of the work in the selected Queue(s) to. Today, this only supports Queues and Agents, due to limitations with the setup of dynamically adding skills in Flow – it is on the near term roadmap to resolve this short-coming.

Required Updates

  • Each of the Route Work actions need to be updated for your Org – select the appropriate channel and routing configuration (where appropriate) to a valid value.
  • For any other types of work you have in your org, you will need to add another ‘Collection Filter’ element to filter for that work, and loop through it.

Optional Updates
None


Reassign

Description:
This flow is designed to be used on any of the tabs in Omni-Supervisor that lists work – such as the Skills Backlog, or any of the sub-tabs of each page. The Flow expects a list of record Ids to be passed in (to the input variable ‘ids’ – this is done by default from Omni-Supervisor) – these could be of many different types. This will show the above screen – where you can select the appropriate Queue or User you want to assign all of the selected work items to. Today, this only supports Queues and Agents, due to limitations with the setup of dynamically adding skills in Flow – it is on the near term roadmap to resolve this short-coming.

Warnings

  • Reassigning work that is currently assigned directly to an agent to a Queue will fail – workaround is to assign to a skill first, before assigning to the queue (can be added to the Flow)
  • Using this action on the ‘Assigned Work’ page may have unexpected consequences – as that work is currently with an agent, and will be re-routed while they are still working on it, potentially leading to multiple people working on the same work item.

Required Updates

  • Each of the Route Work actions need to be updated for your Org – select the appropriate channel and routing configuration (where appropriate) to a valid value.
  • For any other types of work you have in your org, you will need to add another ‘Collection Filter’ element to filter for that work, and loop through it.

Optional Updates


Assign to Me

Description:
This flow is designed to be used on any of the tabs in Omni-Supervisor that lists work – such as the Skills Backlog, or any of the sub-tabs of each page. The Flow expects a list of record Ids to be passed in (to the input variable ‘ids’ – this is done by default from Omni-Supervisor) – these could be of many different types. This will show the above screen – where you can select the appropriate Queue or User you want to assign all of the selected work items to. Today, this only supports Queues and Agents, due to limitations with the setup of dynamically adding skills in Flow – it is on the near term roadmap to resolve this short-coming.

This is the same as the Reassign Flow above, but a faster way to assign to yourself if you want to quickly grab a work item out of the backlog.

Warning – using this action on the ‘Assigned Work’ page may have unexpected consequences – as that work is currently with an agent, and will be re-routed while they are still working on it, potentially leading to multiple people working on the same work item.

Required Updates

  • Each of the Route Work actions need to be updated for your Org – select the appropriate routing configuration to a valid value.
  • For any other types of work you have in your org, you will need to add another ‘Collection Filter’ element to filter for that work, and loop through it.

Optional Updates
None

Writing Test Classes for Invocable Methods and Invocable Variables

If you need a primer on invocable methods or invocable variables, check out this blog. To keep things consistent, this post references the same Product Selector pro-bono project for Playworks.

Here is the inner class of invocable variables.

public class Requests{
        
        @invocableVariable(label='Product Id' required=true)
        public String productId;
        
        @invocableVariable(label='Opportunity Id' required=true)
        public String opportunityId;
        
        @invocableVariable(label='Sales Price' required=true)
        public Double salesPrice;
        
        @invocableVariable(label='Quantity' required=true)
        public Integer quantity;
    }

And here is the invocable method, which takes a list of Requests objects as an input parameter.

@InvocableMethod(label='Create New Opportunity Products')
public static void createNewOpportunityProducts(List<Requests> requests){
        
     Requests request = requests[0];
     List<OpportunityLineItem> oppProds = new List<OpportunityLineItem>();
        
     Opportunity oppty = [SELECT Playworks_Fiscal_Date__c 
                          FROM Opportunity                           
                          WHERE Id =: request.opportunityId]; 
        
     PriceBook2 stdPriceBook = [SELECT Id 
                                FROM Pricebook2                                  
                                WHERE isStandard = True 
                                LIMIT 1];       
        
     PriceBookEntry pbe = [SELECT Id 
                           FROM PriceBookEntry 
                           WHERE Product2Id =: request.productId 
                           AND Pricebook2Id =: stdPriceBook.Id
                           LIMIT 1];       
        
     for(Integer i = 0; i < request.quantity ; i++){
         OpportunityLineItem oppProd = new OpportunityLineItem();
         oppProd.Product2Id = request.productId;
         oppProd.PricebookEntryId = pbe.Id;
         oppProd.OpportunityId = request.opportunityId;
         oppProd.UnitPrice = request.salesPrice;
         oppProd.Quantity = 1;
         oppProds.add(oppProd);
     }
     insert oppProds;
}

Note: The way that this Product Selector flow is designed, only one instance of the Requests class is ever populated. Hence index 0 from List<Requests> is assigned.

Requests request = requests[0];

Typically, Apex tests follow the same basic algorithm.

  • Create the data
  • Run the test
  • Validate the assertions

When writing a test for an invocable method with invocable attributes, there are a couple of things to look out for.

Creating the Data

When the createNewOpportunityProducts invocable method is called by the flow, the flow outputs provide the data. Specifically, the flow maps its output variables to what will become attributes of the Requests object at runtime.

The test class is much more manual. Since there is no flow to pass its output variables, we have to create the necessary data.

Pricebook2 standardPricebook = new Pricebook2();
standardPricebook.Id = Test.getStandardPricebookId();
standardPricebook.isActive = true;
update standardPricebook;
        
Product2 prod = new Product2();
prod.Name = 'Test Prod';
prod.IsActive = True;
prod.Product_Selector__c = True;
insert prod;
        
PricebookEntry pbe = new PricebookEntry();    
pbe.Pricebook2Id = standardPricebook.Id;
pbe.Product2Id = prod.Id;
pbe.IsActive = True;
pbe.UnitPrice = 1000;
insert pbe;
        
Opportunity opp = new Opportunity();
opp.Name = 'Test Oppty';
opp.StageName = 'Prospecting';
opp.CloseDate = Date.today();
insert opp;

Once the data is ready to map, we instantiate an object of the Requests class. To do this, we start with the name of the class that contains the invocable method: ProductSelector_Create. That is followed by dot notation to reference the inner class Requests.

List<ProductSelector_Create.Requests> lpscr = new List<ProductSelector_Create.Requests>();

Remember, only one instance of the Requests class is ever populated. So next we instantiate a single ProductSelector_Create.Requests object, map the data that we created earlier to its attributes, and add it to the list of ProductSelector_Create.Requests.

ProductSelector_Create.Requests pscr = new ProductSelector_Create.Requests();
pscr.productId = prod.Id;
pscr.opportunityId = opp.Id;
pscr.salesPrice = 800;
pscr.quantity = 2;
lpscr.add(pscr);

Now we have an input parameter to pass into the createNewOpportunityProducts invocable method.

Running the Test

There is one more thing to be aware of. When an Apex class is called by an Apex trigger, the DML causes the trigger to fire. Since we’re testing an invocable method that is called by a screen flow, no such mechanism exists. Instead of using a DML, we have to explicitly invoke the method, passing in the input parameter we just created.

ProductSelector_Create.createNewOpportunityProducts(lpscr);

Validating the Assertions

Thankfully, there are no real gotchas here. In the Product Selector example, verify that the quantity of 2 caused 2 opportunity products to be created.

Introducing: User-aware Record Type Picker for Flows

Update October 15, 2022: install links updated to latest version
Update September 26, 2022: install links updated to latest version

Next week is the year’s big Main Event: Dreamforce. For some of us, that means packing our things and scrambling to fill our session schedules, make meetup plans, and find the perfect dinner spot. For others, like me, an sense of impending FOMO.

Fortunately, the announcements made at Dreamforce, and the Winter 23 release just a few weeks after, will be for everyone, whether you make it in to San Francisco or not. If you’ve reviewed the release notes or scoped out your preview sandbox, one item you may notice is that the Dynamic Fields feature for Screen Flows will be Generally Available, and with that, picklist fields will be Record Type-aware. This means you can simply place a record variable’s picklist field on a screen, and if you’ve set the Record Type for that record, it will correctly display only the picklist values that are active for that Record Type. However, that Record Type must be set on the record variable before the Flow arrives on that Screen with the picklist. So, then: what options do we have today for letting a user select a Record Type?

Today’s Options for RecordType Selection in Flows

  • You could build a Choice for each Record Type. That might work, but what if different users have a different permissions to different Record Types. Sounds like a pretty hairy mess to try to get right, and to maintain moving forward. Not a great option.
  • You could do a Record Choice Set or a Collection Choice Set based on the RecordType object. Of course! Perhaps by setting the Screen Flow to run in User Mode, which respects a user’s access to records, we’ll only get the Record Types that the user has permissions for. If you’ve tried this, you’ll quickly learn that the RecordType object, which holds all each object’s Record Types, is not limited by the user’s permissions. If a user has read access to an object, they’ll have access to every RecordType for that object, even if they don’t have permission to create a record for that Record Type.
  • The closest I could find is the GetRecordTypeInfo action in the UnofficialSF Flow Action Pack. Its a nifty action! But its a bit incomplete, as it requires you to pair it with another screen component, it won’t give you the Record Type Descriptions, and the Record Type information is provided in separate text collections, making it challenging to be able to use the selected option downstream in the Flow.

🎉 Welcome to the team, Record Type Picker 🎉

The Record Type Picker is an open-source, free-to-use component that can be used in Screen Flows to give users selectable choices of Record Types, automatically filtered to just the Record Types that the running user has access to!

Three display types

Visual Picker, Radio Group, Picklist, each with optional Show or Hide Descriptions

Screenshot of the three display options available for this Record Type Picker component

Optional Auto-Next Navigation on Select

Auto navigate on selection works best with the Visual Picker on a screen that doesn’t have any other inputs and has the footer and/or Next button hidden. When the user clicks on a selection, the Flow will automatically navigate to the next screen. It is also recommended to provide a “Previous” button on the next screen so that the user has a chance change their selection if they’ve made a mistake.

Easy configuration with a Custom Property Editor that has a Preview mode

Bonus Invocable Action

The package also comes bundled with an Invocable Action to get a Collection of RecordTypes available to the running user, in case you’d prefer to use other screen components like Collection Choices or Data Table

Install with Ease

This component does not depend on any other packages, and can be installed with an unlocked, namespaced package using the links below. The namespace means you don’t have to worry about conflicts with anything you’ve already got in your org, and being unlocked gives you the confidence that what you see in the source repo is what you get in the package.

Package version last updated: October 15 2022

Source

There are a handful of features that I’d like to add, but its at the point now that it should be pretty useful for anyone that needs this kind of thing. Take a look, and please add any issues or feature requests to the Issues log in the GitHub repo!



Find The Object Type Of Any Id Value Using Flows

If you’ve been in the Salesforce ecosystem for long enough, you’ll eventually find yourself working with Record IDs that could be multiple types of objects, and you need to build a report or automation that needs to know what kind of object it is. A common example of this might be a task or activity report grouped by the Type of object each Task is related to. It usually involves adding a Formula(Text) field to the Activity object that translates the record Id in the Related To field on Task/Activity to the name of the Type of Object it is:

That kind of formula gives you a field on the Activity object that will tell you what kind of object it is related to! You can imagine all of the nifty reports this could enable, or Flow-based automation you could build that may be specific to Activities that are related to a particular kind of object.

For many years, it was really the best we could do without writing Apex code. In this post, we will walk through the limitations of this approach, and how to build a better version of this in a Record-Triggered Flow.

A better approach with Flows

With today’s automation tools, and Flow Builder in particular, we can account for any object, without any hardcoding, and without a single line of Apex. The Flow itself is simple: just two elements in a Record-Triggered Flow. So if you’re short on time and want to skip the explanations, jump on ahead to “Let’s Build”, and be on your way. But I think its helpful to know the how and the way, so let’s dive in.

Secret Sauce Ingredient 1: Key Prefix

Record ID values are 15 or 18 digit values that uniquely identify Salesforce records. We won’t go into everything that goes into constructing them, except for one crucial bit of information: the first three characters of every ID identifies the object type of that ID! This is called the Key Prefix. Each object – standard or custom – has a unique 3 character Key Prefix and *every record* in that object starts with those three characters. That’s why the formula above takes the first three characters of the ID LEFT(WhatId,3) and then uses the CASE() function to translate that to an object name.

There are some important things to know about the Key Prefix:

  • Standard Salesforce Objects (objects that don’t have a suffix like __c at the end) all each have the same key prefix in every org in every environment across all of Salesforce. Account is always 001, Opportunity is always 006, etc
  • Custom Objects are not guaranteed to be the same across orgs. Even though you may find some of your custom objects are the same between Production and Sandbox, this is not guaranteed and should not be relied upon.

Given this information, have you caught the limitations of the formula approach yet? There’s two huge red flags:

  • It requires you to hardcode the 3 digit key prefix to object translations! If you build this formula field in Sandbox (which you should definitely be doing!) to include a Custom Object, there is no guarantee that the same object will have the same key prefix when you move it to production, leaving you scratching your head, then needing to make a change directly in Production (which you should definitely NOT be doing!)
  • It requires you to explicitly write out each object you want to translate from key prefix into an object name. This means writing out a /massive/ formula to account for all your objects, or lots of maintenance when someone inevitably asks why it doesn’t work for their favorite custom object that wasn’t included.

There is a better way. Enter Flows, and Secret Sauce Ingredient 2.

Secret Sauce Ingredient 2: EntityDefinition

When building automation in Flow Builder, we all know you can retrieve a record, or collections of records, from every day objects like Account and your Custom Objects, but did you know there are a whole host of system objects you can retrieve records from using a simple Get element? One of these objects is what is going to help us today: EntityDefinition. This object contains metadata and configuration information about each of the Standard and Custom Objects in your org. Each object has a record in this EntityDefinition object. Information like Label, API Name, Plural Label, and more.

And – you guessed it – EntityDefinition has a field with each object’s Key Prefix.

Let’s build!

Now that we’ve got our two ingredients, let’s build a better alternative to that formula above. Let’s take one moment to ask ourselves what we need first:

We want a field on Activity that contains the name of the Object Type for the record it has in its WhatId field (the label for the WhatId field is “Related To”).

Add a Text Field to Task

Instead of a formula field, we’re going to add a regular Text field to the Activity object, and we’re going to call it something like “Related To Type” (that label is my preference, but whatever makes sense to you and your team)

Add a Record-Triggered Flow on Task

Entry Criteria

Next we’re going to build some automation to populate this field. We’ll add a Record-Triggered Flow on the Activity Object to do this. Since an existing Task could be updated to change which record is in the Related To field, we’ll want this Flow to run when records are Created and Updated

And we’ll want to configure Entry Criteria so we’re only running this Flow when we need to, and not when we don’t. We can use the (new as of Summer 22 release) Entry Criteria Formula feature to do this.

In short, this Entry Criteria formula will ensure this Flow only runs if its a newly created Task record that has a WhatId value, or if its an update on an existing Task and this update includes a change to the WhatId value, which are the scenarios we’ll need to set or change our Related To Type field.

(ISNEW() && NOT(ISBLANK({!$Record.WhatId})))
 || ISCHANGED({!$Record.WhatId})

Lastly for the Start Configuration, don’t forget to take advantage of our ability to run this as a Fast Field Update, since all we are doing is setting a value in a field on the same object that triggered the flow.

Get the Related EntityDefinition

Without further ado, let’s use our secret sauce ingredients to build this Flow. The Flow has our $Record variable with the WhatId, and the EntityDefinition object is what has the link we need between the KeyPrefix from that $Record.WhatId and the Object’s name.

So we want a Get on EntityDefinition, and we’ll add filter conditions to match the KeyPrefix against a Formula Resource that returns the first 3 characters of $Record.WhatId field

Formula Resource (named relatedToKeyPrefix):

LEFT({!$Record.WhatId},3)

GOTCHA ALERT: There are a handful of standard, system objects listed in EntityDefinition that have no KeyPrefix value. They cannot be associated with Tasks, so we don’t really need to worry about needing to match on them. But, we do want to clear our field if a task gets its related to field cleared to be empty, we want to avoid accidentally matching on one of these “empty KeyPrefix” objects. Its a simple condition we need to add to the Get:

KeyPrefix Does Not Equal {!$GlobalConstant.EmptyString}

Next, in the Flow Builder after the Get, we’ll add an Update element that sets the Related To Type field on our Task record using the result of this Get! It really is that easy. The field from the EntityDefinition that we’ll use here is MasterLabel.

Back in the Flow Builder Canvas, our resulting flow is just two elements!

Now, you’re going to build a Flow Test for this before deploying to production, aren’t you?

⚠ Consider Existing Data ⚠

One difference from the formula approach, and consideration you’ll have to account for with this approach, is your existing data. With a formula field, you don’t have to make any data changes- its just a formula that is evaluated anytime someone or something looks at the record. With this approach, once the Record-Triggered Flow is in place, all records *moving forward* will have your new field populated, but your existing records won’t. This means you’ll need to backfill them, or tweak the entry criteria and run a one-time “backfill” update. Use whichever data tool you’re most comfortable using to do this.

Take it further

Add a Related To API Name

The object’s Label was useful for human-readable outputs like the reports we discussed in the beginning, but you may find a lot of use cases where its better to have a unique-and-still-human-readable value, which is what the API name is designed to be. So, add a Related To Type API Name text field to the Activity object, and simply add it alongside the Update element we already have for the Related To Type field.

Ultimate Picklist Approach

If you’re really clever, and you have a stable set of objects, you could instead use a picklist. If you have a picklist with values that equate to your object labels and api names, then your Flow just needs to update it with the EntityDefinition’s QualifiedApiName value, and then you get the best of both worlds in one field. Reports and Record Views show the label (and can even be translatable!), while can manage it using api name.

Decision Branching

This becomes very powerful if you’ve got different actions you’ll need to take based on different objects, because you can simply add Outcomes to the decision for each object type, and configure its path with Object Type-specific actions accordingly.

Flow Use Case: Create a filtered opportunity list view based on account team membership with Data Table and the new In Operator

The Winter ’23 release brings with it some very nice additions to flows. I set out to try out the new features with a use case that was impossible to do before without resorting to custom functionality
Use Case: Display all open opportunities for a selected user who is on the account team.
Problem statement: Since both opportunities and account team members are related to account, this cannot be done with standard reports. You cannot create a report type with one parent and two related objects.
I was able to accomplish this task using a flow combining some new features and some old favorites from UnofficialSF

Winter ’23 new features used
IN Operator
Data Table (beta)

UnofficialSF components
Quick Lookup flow screen component
Extract Strings from Collection flow action (part of Collection Processors)

The flow starts by displaying a user lookup for the selection. I had to use this custom lookup screen component since the standard one does not support a custom lookup field to a user record.

The selected UserId is then used to get all account team members for where the user is on the team

The next steps will be to get a collection of account Ids that can be used to get the open opportunities. For this I used the handy Extract Strings from Collection action instead of looping through the account team members and adding to a collection.

Now comes the fun part. I can use the new IN Operator to get all open opportunities where the opportunity’s account is in the account ID collection

I wanted to filter the account to get only the accounts that had open opportunities so I used another Extract Strings from Collection using the account Ids from the open opps. The dedupe function of the action ensures each account will only appear once in the resulting list (it is set to true by default)

Now that I had only the accounts of open opportunities, I wanted to filter my original account team members collection to only members from these accounts.

Alternatively you can use another get records IN, but this will consume another DML which can be avoided.

The next step was to prepare two more formula fields so I could display a link to the opportunity account name and the account name for the team member

Time to see the new Data Table (beta) in action
This is the open opportunities table

And the Account team table

Here is the completed flow

I added the flow to a Lightning app page and here is how the finished flow works

So simple and yet so powerful. I’m excited for all the new possibilities these new features bring to flow and looking forward to exploring more use cases.

Summer ’22: External Services Updates

Check out some of the changes and enhancements to External Services in the Summer ’22 release!

Call External Services Registrations Natively from Apex

Now you can access External Services registered actions directly from Apex to tap into reusable functionality when writing Apex code. Previously, actions created through External Services were exposed exclusively for invocation through Flow or Einstein Bots.

Check out the Summer ’22 Release Readiness Live demo (starts at minute ~3:20) and Developers’ Blog for a simple walkthrough of how to use this functionality.

Make sure to check out the official Salesforce Help Docs: Invoke External Service Callouts Using Apex for more details!

Salesforce Developer Console

Update an Existing Schema Connected to Flow

If a registration is in use by a flow, now you can update it with a new, compatible API specification version. Previously, you couldn’t update a registered schema that was in use by a flow. If the new schema version isn’t compatible, the edit workflow notifies you which operations and schema objects are in use by which flows and by which Apex classes. With this information, you know which existing references are incompatible so that you can remove them before saving your updated registration.

For details about supported and non-supported changes, see the official Salesforce Help Docs: Appendix 1: Schema Update Considerations.

External Services: Edit Screen

Register More APIs

Now you can register an external service without manually editing the schema before registration to conform to the 80-character limit for derived operation and object developer names. This enables you to register more APIs with less friction!

Additional Details

For additional details, make sure to review the Summer 22′ Release Notes and keep the feedback coming in the External Services Trailblazer Community!

Progress Bar component for flow screens

Salesforce Labs has published a new flow screen component that has 6 different indicator types to let users know where they are in your Flow-based process. In the spirit of Salesforce Labs, they are also allowing access to the source code. Here are the installation and configuration instructions

Check it out

Undeleting Records with Flow

Created by Yumi Ibrahimzade


ABOUT

You can perform insert, update and delete operations in Salesforce Flow. Most of the time, they are enough but what if you want to undelete records from the recycle bin? There isn’t undelete records operation in Flow. However, you can achieve this using this Invocable Apex Action.

See this post for the main discussion of this new Flow capability.

INSTALLATION

Production or Developer Edition

Sandbox

PARAMETERS

recordIds – Enter a text collection of record Ids to undelete.

CONSIDERATIONS

This Invocable Apex Action undeletes the records from the Recycle Bin. If the record is permanently deleted, which means that it is not in the Recycle Bin anymore, you cannot undelete it.

HOW TO USE

Add an Action element from the Toolbox and search for FlowUndeleteRecords. Pass the text collection of the record Ids to the input parameter called recordIds. Even if you want to pass a single record Id, you still need to create a text collection and pass it as the input value.

undelete records action and parameters

Read this post to learn how to find the deleted record Ids and see an example of undeleting records using this Invocable Apex Action.

DEMO