Updates to Dedupe Record Collections Action & ExtractStringsFromCollection Action

Update 5/18/20

The ‘Dedupe Fields From Record Collection‘ action is being retired and the functionality is now being moved into the CollectionsProcessors ‘ExtractFieldsFromCollection’ action:

Ragan Walker added several improvements to the action:

  • Parameterizes dedupeValues functionality (defaults to true)
  • You can now output to a comma-separated text variable instead of just a text collection variable
  • Supports extracting + deduping of multi-select picklist fields
  • Adds flow documentation
  • Supports bulkification
  • Adds exception handling to make action more robust
  • Adds further unit testing

You can still download the ‘DedupeRecordsFromCollection’ action below.


A few months back I posted about an action Danny Tilton and I over at CapTech Consulting worked on that has simplified a ton of my Flows. You can find that on my LinkedIn profile here (sorry, shameless plug: Here)

Code files can be found here https://github.com/alexed1/LightningFlowComponents/tree/master/flow_action_components

New Version Highlights

  • We separated the action into two classes: DedupeFieldsFromRecordCollection and DedupeRecordCollection to simplify its usage.
  • Added a couple error exceptions – just in case!
  • You can now output results from DedupeFieldsFromRecordCollection to a comma-separated text variable (instead of just a collection)!

Dedupe Fields From Record Collection

NOTE: THIS ACTION IS NOW RETIRED IN FAVOR OF REVAMPING THE ‘ExtractStringFromCollection’ action. See the update at the beginning of this post.


This class takes in a Record Collection and spits out all of the de-duped values of a field of your choosing across the records in the collection.

I used this in my LinkedIn Post, but say you wanted to get a list of all of the Owners of a bunch of records, but you didnt want a bunch of duplicate IDs. Run this bad boy on your collection and you’ll get a collection or text string of unique IDs! You can that loop on that to send an email alert, for example.


  • Object Type
  • InputRecordCollection – The collection you want to get the de-duped field from
  • fieldToDedupeOn – The API Name of the unique field values you want to return in your text collection


Dedupe Record Collection

Installation Link: https://login.salesforce.com/packaging/installPackage.apexp?p0=04t4T000000x9LA

Sandbox Link (recommended): https://test.salesforce.com/packaging/installPackage.apexp?p0=04t4T000000x9LA


This is quite similar to the other class – instead of the action returning text, it just returns a set of de-duped records based on the field you specify.

In the example above you could de-dupe a set of Accounts based on OwnerId, then loop over each of the Accounts to grab its Owner to email on a nightly schedule. I think where this would come in handy is if you needed to grab other details about the record you’re de-duping on. Let’s say in your email to the Record Owner you wanted to give a few details about the record – that’s where you’d want this action.


  • Object Type
  • inputRecordCollection – The collection you want to de-dupe
  • fieldToDedupeOn – The API Name of the unique field values you want to de-dupe all of the records on


  • Object Type
  • outputRecordCollection

How to use ISCHANGED and PRIORVALUE in Before Save Flows by GORAV SETH

GORAV SETH demonstrates how you can use ISCHANGED and PRIORVALUE in Before Save Flows
Check it out

Get Information About A Flow with GetFlowMetadata

The Automation Components repo that was published last week features a useful action that I don’t think I ever created a blog post for.

GetFlowMetadata (what’s it’s called in the Automation Components repo) provides metadata about a Flow. Just provide it with a name OR a recordId and it returns:

        public String FlowApiName;

        public String FlowDefinitionId; 
        public String ActiveVersionId; 
        public String Description; 

        public String Label; 

        public String LatestVersionId; 

        public String NamespacePrefix; 
        public String FlowType; 


Install this from the new Automation Components repo via a managed package


View Source

Automation Component Source Docs

How to launch Flows from Your Own Lightning Web Component

Target Persona: Developer

If you’re working in Aura, you can insert the lighting:flow component. This is the same component that you can insert into pages in App Builder. However, Salesforce has not yet made lightning:flow available in LWC. Because LWC does not allow the embedding of Aura components, you can’t just add the lightning:flow component to your LWC. Why isn’t it available yet? lightning:flow is probably the most complex page component and has to be completely rewritten to work in LWC. It’s on the way, but it’s taking time.

The current options for working around this depend on whether you want to execute a visual flow that has screens and requires a user session with a browser, or whether you want to execute a screen-less ‘Autolaunched’ flow.

For Screen Flows

Wrapping lightning:flow and your LWC in an Aura wrapper component, and sending messages from your LWC to control the Flow component. Check it out here.

You can have your component load the Flow by URL. This will either load the Flow into a new tab or a new window, or you can have it overwrite your existing page (and your existing LWC).

For Autolaunched Flows

If the Flow is an Autolaunched flow (and thus doesn’t have screens) you can dynamically launch it from your LWC code by using an Apex call to the Flow invocation point.

Flows can be launched from platform events as of Summer ’20, so you can have your LWC fire a platform event. This also only works with screen-less flows.

A Big Honor For UnofficialSF: Some of Our Components Just Got ‘Officialized’!

As you can see from this new post from Salesforce evangelism, Salesforce has officially recognized the work being done by the Flowhana, and in particular by Eric Smith (Quick Choice), Chris Van Der Merwe (Calculate Business Hours), David Entremont (Save Records Async), Enrico Murru (Evaluate Formula), and Andrii Kraiev (many of the actions). I worked with Philippe Ozil from Developer Evangelism, as he picked a selection of components, polished them up, added tests, and then created a new Automation Components repo.

I’m pleased with this on several levels:

  • It will attract new members to the Flowhana and introduce people to UnofficialSF
  • The code has gotten a polish and review by some very seasoned Salesforce developers who know a lot of useful things
  • The components have been made available as managed packages. Usually, we haven’t budgeted the time to create managed package versions on UnofficialSF

Managing Things Going Forward

Developers can contribute code changes via pull requests to the Automation Components actions, and they can continue to contribute to UnofficialSF. The process for Automation Components is more formal than what we do here. Philippe, the repository manager, says “it’s good to validate interest with a question first then have a PR. Then let’s be clear about best practices and code coverage reqs.”

Convert Records to a CSV File

Ragan Walker and I over at CapTech Consulting have been hard at work at an insanely useful Generate CSV invoked action for you all. I consider this the inverse of Narender Singh’s ‘Create Records from CSV‘ action. We built this to fully utilize some of UnofficialSF’s awesome custom actions like ExcecuteSOQL, Send Rich Text Email, and Get Field Names from a Layout.


We created a little video showing off the examples at the bottom of the post – Check it out

Get the Code & Unmanaged Package

If you’d like to contribute to the code or copy/paste into your own org, feel free to do so on CapTech’s Github repo: https://github.com/captechconsulting/lightning-flow-utils/tree/master/convert-records-to-csv

Click below for an unmanaged package of the two apex classes (Class and Test Class).

Production (not recommended – test it first!): Here
Sandbox: Here

A Note on Files

If you arent familiar with the data model / structure of Salesforce Files (ContentDocumentLink,ContentDocument,ContentVersion) I STRONGLY recommend getting familiar with it. Not only because it will help you understand this action better, but because every admin and developer should know how they function.

Alex Edelstein also wrote a very helpful article on the matter here: https://unofficialsf.com/using-files-and-attachments-in-flow/ We built this to easily support providing a collection of ContentDocumentLinks to feed straight into the Send Rich Text Email action!

Use Cases

  • Generate a CSV file and Email it to a group of people, even non-SF Users using Send Rich Text Email
  • Generate a CSV file and post it to a Chatter Group or a Document Library
  • Create a screen flow triggered from a Related List, List View, or Quick Action that lets users pick records using Datatable and output their selections to a CSV file, then email that file to a set of users that they pick.
  • Send a CSV of records to an another system using invoked Apex Actions
  • Generate a CSV file from a ‘Complicated Collection’ – Utilize UnofficialSF’s Collections Processors to create collections of records that would be impossible with Salesforce Reports or even SOQL


  • Create Multiple ContentDocumentLinks – Linkages between File and Records – Specify a string or text collection of record IDs to associate the file with those records
  • Sharing / Collaborator Rights – Specify the sharing and visibility of each ContentDocumentLink
  • Limit the Fields Exported to CSV – Customize the order and fields included in your CSV via text collection or comma-seperated string. This is optional, if left blank, it will export all fields that were in the collection / query.
  • Aggregate Queries and Custom Relationship Support* – Support for relationship fields provided by ExcecuteSOQL Example: Contact__r.Custom_Field__c.
  • Run Asynchronously – Can execute ‘asynchronously’ if CPU timing is an issue.
  • Generate a Platform Event if ran asynchronously – If ran asynchronously, supports generating a platform event of type ‘CSV_Document__e’. The platform event does not come packaged with this, you will need to create it manually for now. See below for when and why you’d want to use this.
  • Error Handling – We added some error handling for a variety of scenarios – including if you hit CPU limits we suggest switching you into asynchronous mode



  • recordCollection
    • Record (sObject) Collection – The collection you want to turn into a CSV
  • documentTitle
    • Text – The title of the File when generated


  • documentShareType
    • Text – Sharing permissions for the file. Available values: “V” (viewer), “C” (collaborator), “I” (inferred); Default value: “V”
  • documentVisibility
    • Text – Specifies whether the document is available to all users, internal users, or shared users. Available values: “AllUsers”, “InternalUsers”, “SharedUsers”; Default value: “AllUsers”
  • linkedRecordIds
    • Text Collection – List of recordIds to link generated document to. If this and linkedRecordIdsString are left blank, will only relate to the running user
  • linkedRecordIdsString
    • Text – Comma-separated string of recordIds to link generated document to. If this and linkedRecordIds are left blank, will only relate to the running user
  • fieldsCollection
    • Text – Collection of strings of record fields names to print as columns in CSV. If this and fieldsString are empty, all populated fields on the record collection will be displayed.
    • IMPORTANT: If you want to include related fields from a SOQL query (Custom_Object__r.Custom__c) this parameter is required.
    • If you used field aliasing in an aggregate query, alias names can be provided rather than the API names.
  • fieldsString
    • Text – Comma-separated string of record fields names to print as columns in CSV. If this and fieldsCollection are empty, all populated fields on the record collection will be displayed.
    • IMPORTANT: If you want to include related fields from a SOQL query (Custom_Object__r.Custom__c) this parameter is required.
    • If you used field aliasing in an aggregate query, alias names can be provided rather than the API names.
  • executeAsyncIdentifier
    • Text – If provided, the action will execute asynchronously and return values will be posted to a platform event: CSV_Document__e. Use this identifier to listen for the platform event in a ‘Wait’ element in flow or use Summer 20’s ‘Invoke Flow from a Platform Event’.
    • Note: You generally want to pass in the Flow’s Interview ID (Done by accessing the Flow global variable $Flow.InterviewGuid

Outputs (Optional)

If the action is executed asynchronously (indicated via executeAsyncIdentifier parameter), then the job id(s) for the execution are returned immediately and the document information is published via platform event if configured. Otherwise when run synchronously the CSV File information is returned immediately.

  • ContentDocumentID
    • Text – ID of the ContentDocument generated
  • ContentVersionID
    • Text – ID of the ContentVersion generated
  • ContentDocumentLinkIDs
    • Text Collection – IDs of the ContentDocumentLinks generated
  • asyncJobIDs
    • Text Collection – Id of the asynchronous job queued (AsyncApexJob). If the action was executed asynchronously all other return values will be null. Use this to query for status of the job.


When using this action, you are still subject to normal governor limits, even when running this action asynchronously. We’ve added customized error handling should you input a collection of records that exceed the available heap limit.


Create a CSV file then post it to a Chatter Group

One potential use case here could be generating a CSV from a Flow lookup, then posting the file generated to a Chatter Group AND to a specific Account. Here’s how we’ll relate the file to multiple entities using a collection of ContentDocumentLinks.

Here’s how you’d do it:

  • Generate whatever Record Collection you want from a Flow Get step. Feel free to choose ‘All fields’ but beware – you will want to limit the fields when you configure the action or you’ll get every field on the object in your file.
  • Do another Lookup on ‘CollaborationGroup’ using the group name to get the ID of the Chatter Group
  • Create a Text variable and check ‘Allow multiple values (collection)’ This is what you’ll be adding your related IDs to.
  • Create an assignment step to add both the ‘recordId’ variable used to launch the flow and the results of the Chatter Group lookup Id {!Look_up_Chatter_Group_ID.Id}.
  • We’re ready to generate our CSV! See below for how you’d configure it. For documentTitle I recommend a formula field and grab the related record’s details and the TODAY() function to add to the file name. Check out the ContentDocumentLink documentation linked above for a good description on what each of the values mean for ShareType and Visibility.
  • Your CSV will now be in the ‘Files’ related list of any records you put into the linkedRecordIDs String/Collection!
    • Note: If you don’t specify any Linked Records, the File will simply be associated to the user that runs the Flow.
  • If you want to create a chatter post to the group then relate the file to the post as well, you can use the out of the box ‘post to chatter’ action and then do a ‘Create’ step on ‘FeeedAttachment’ using the output ID of the chatter step and the outputs from the CSV action:

Output aggregate queries with ExecuteSOQL

This action is also supports aggregate querying with Alias support with ExecuteSOQL.

Aggregate functions in SOQL, such as SUM() and MAX(), allow you to roll up and summarize your data in a query. For more information on aggregate functions, see “Aggregate Functions” in the Salesforce SOQL and SOSL Reference Guide.

Aggregate Queries

Let’s try passing in a query string from ExecuteSOQL into the CSV action – including an Alias for our Count expression. By providing an alias for our count, the CSV will have a nice clean column name instead of some nasty ‘expr0′ label.

SELECT count(Id) NumberOfAccounts, Type
FROM Account
WHERE type != null
Group by Type

  • Run the ExecuteSOQL action using the query above in a Text Template variable as the ‘soqlQuery’ input.
  • NOTE: MAKE SURE YOU USE PLAIN TEXT! There’s also a bug in flow where if you make any changes to the text and save it, it will revert to rich text. Every time you edit it, you will need to flip it to plain text.
  • Run the action with the output from the ExecuteSOQL action and voila!

And the output file:

Related Fields

When paired with ExecuteSOQL you can also output related fields! Check out the video for how to do this. The key thing here is that you need to make sure all the fields in your SELECT statement are in the ‘fieldsString’ input. So if the query you pass into ExecuteSOQL is:

select id, name,Type, Ultimate_Parent_Company__r.Name from Account where Id = ‘{!recordId}’

You should be putting this into the fieldsString input for the CSV action:

TIP: It might be easier and more maintainable if you store your fields in a text variable so you can reference them in one place.

Wait for a Platform Event and Send an Email with the File Attached.

  • Create a new Platform Event type – call it ‘CSV Export Event’ and create the fields exactly as shown in the code below.
  • Comment back in the code referencing the platform event in the Apex Class (aka remove all the backslashes)

Your uncommented code should look like this:

  • You’re now ready to utilize the Platform Event functionality! The next step is really up to you and your process. In Summer ’20 you can choose to launch a flow based on this platform event type, or you can incorporate a ‘Wait’ element and resume on a platform event being posted. See below for an example.
  • For the CSV action you want to populate the executeAsyncIdentifier input variable with the Flow Interview ID:
  • Here’s how you’d configure it the Resume piece of your ‘Pause’ action, :
  • You can store the Platform Event Response in a ‘record variable’ for use later in the flow. Just be sure to assign anything you might want from it in a separate variable like below:
  • Using the response from the platform event, you can use the Response’s ContentDocumentID to then do a ‘Get’ on ContentDocumentLink using the response ID and store it in a Record Collection Variable to be used in the handy dandy Send HTML Action. Make sure you store All Records.
  • In your Send HTML Action configuration just throw in the results of the ‘Get’ above and you’re all set to send attachments!

Here’s the full picture:

The SOQL Wins Keep Coming: Execute SOQL action now supports date literals and ID literals

With version 1.3.2 of Execute SOQL, you can now pass in queries like these in your Flow and get back records:

SELECT LastName from Contact WHERE Id = '1123xxx333e3e'

Query on date literal:
SELECT Id from Contact WHERE CreatedDate = LAST_n_DAYS:14

Kudos to new contributor Ragan Walker, part of the impressive operation at Captech Consulting. Keep an eye out: I hear they’re about to post a crazy CSV utility.

Check it out!

Easy SOSL Searching with ‘Search with Apex’ from GravityLab

The community has done some great work bringing SOQL tools to Flow, but this recently published action from GravityLab demonstrates how you can use the recently shipped dynamic SObject support to allow for the first time, record searches in flow to leverage the same search Salesforce offers in the Omnisearch bar, complete with fuzzy logic and incomplete words.

It also supports SOQL queries. Some of its distinctive features:

  1. Bypass sharing rules
  2. Pass in a List of Ids.

They’ve provided a video, source code and an installable package. Check it Out!

Mix Callouts with Scheduled Flows with the Updated Launch Flow Dynamically

Many invocable actions make web callouts to endpoints via HTTP. However, Salesforce won’t allow callouts when a transaction is open. So if you want to make a callout in your flow, you need to make sure that any open transactions are closed.

The recently added Scheduled Flows feature always opens a transaction when the schedule ‘fires’, and you can’t insert actions with callouts in Scheduled Flows as a result. That means that something basic like this won’t work:

However, you can use the Launch Flow Dynamically action to work around this limitation. This flow previously was discussed here. The new version 1.2 has a boolean attribute called UseFutureLaunch that can be set to True to cause the dynamically launched flow to get ‘Future’ treatment.

When an action method gets marked as a Future method, Salesforce puts it in a different queue and does not enforce the limitation that there can be no open transactions. In exchange for this, you’re basically allowing Salesforce to delay carrying out the work until some hypothetical future point when there’s less load. In practice, I haven’t see these delays be meaningful but they should be kept in mind.

When you’re using this approach, you break things into two pieces. The callouts get put in a separate, activated autolaunched Flow:

In the Scheduled Flow, add this Load Flow Dynamically action:

…and give it the name of your activated autolaunched flow, along with any parameters you want to pass.

At the moment, the parameters have to be strings, which makes it hard to pass the records that you can get as part of the schedule trigger batch query. However, you can work around that if you need to by using Serialize and Deserialize: have the Scheduled flow serialize the retrieved record, pass it to Launch Flow Dynamically as a string input parameter, and then Deserialize it into a record.

For developers, you can learn about Future methods here.


V1.2 of LaunchFlowDynamically

View Source