Invocable methods used with Flow allow you to launch something in an admin friendly format that uses the massive power of Apex. For example, you have an intake screen that collects answers to a few questions, then you use Apex to loop through many related records dispersing those answers in places hard to reach from Flow.
Creating an invocable method in a nutshell: First you write an apex class with @invocable method (label and description) and whatever code you want the apex to do (easy, right?) Then make your Flow including your input and output variables. Then add an Apex action in Flow to send/receive those variables.
Here are some things I learned about sending data between Flow and Apex.
Invocable methods in Apex always receive a List and they return a List unless the return type is null. Read more under “Inputs and Outputs” here.
Sent | Sent From | Received by | Received |
Record Variable.id | Flow | Apex | List<Id> listOfIds |
Record Collection Variable | Flow | Apex | List<List<Opportunity>> nameOfThis |
List containing 1 sObject record | Apex | Flow | Record (single) variable |
List of Lists of sObject | Apex | Flow | Record Collection Variable |
This is NOT an exhaustive list at all. I didn’t try sending a record variable (not just the ID from Flow), but I assume that will work. There are also generic sObjects that are pretty special, but I didn’t try.
Here are two examples of invocable methods used with Flows. Note that what you send to apex does not impact what you can receive, as far as I know.
Example One: Send One Id, Get Back a Record Collection

- I sent a record id stored in a record variable from Flow to Apex.
- The apex receives it as a List of Ids, even though it is just one id. (The apex input parameter is (List <Id> nameOfList)
- To return a List of Lists start by making a list of an sObject type. [The next part is just an example. Substitute whatever code fits your needs.] I used SOQL to make a list for all Opportunities whose Primary Contact is the recordId sent from the Flow.
- Add that List to a “List of Lists” of that same sObject type (yeah, weird, right?)
- So my original List has one item, and my List of Lists has only one List. You with me?
- My method returns that List of Lists of Opportunities and the Flow receives it as a Collection Variable!

public class contactsListAction {
//this invocable method takes a single contact id from Flow from a record variable
//and returns a list of lists of opportunities associated with that contact.
//the list of lists is stored as a collection variable in the Flow.
@InvocableMethod (label = 'SendContactsGet Opps' description = 'returns opps for this contact.')
//this method returns a List of Lists of Opps to the flow and receives a List of Ids
public static List<List<Opportunity>> getContactIds (List<ID> ids) {
//Store in a list the id, closedate and Amounts of opps whose Primary Contact came in the variable "ids"
//and who have a stage of Clsoed Won and a Close Date of this year
List<Opportunity> opps = [SELECT id,CloseDate,Amount
FROM Opportunity
WHERE npsp__Primary_Contact__c in :ids
AND StageName = 'Closed Won'
AND CloseDate = THIS_YEAR];
//declare a new list of lists of opps
List<List<Opportunity>> itemListList = new List<List<Opportunity>>();
//add the list opps to the list of lists
itemListList.add(opps);
// send list of lists to the Flow
return itemListList;
}
}
Example Two: Send a Collection, Receive Back One Record

- I sent a record collection variable from the Flow to Apex.
- The apex receives it as a List of Lists of type Opportunity.
- [This part is just an example so can substitute whatever code you want here.] I’m finding the first List in the List of Lists, then finding the first Opp in that List and saving that as an opportunity record. I modify the value of the opp here.
- I now have one opportunity record that I need to add to a new list of Opportunities.
- I return that List (that just has one item in it) to the Flow.

public class sendManyGetOne {
@InvocableMethod (label = 'Send Many Get One' description = 'send a collection of records to Apex, get one record back.')
//this method returns a List containing one opportunity
//the parameters are a List of Lists of Opportunities. The flow sends this as a collection variable of opps.
public static List<Opportunity> getOppId ( List<List<Opportunity>> listOppLists) {
//get the first List from the List of Lists
List<Opportunity> oppsList = listOppLists.get(0);
//get the id and Amount from the Opp in that first List.
List<Opportunity> opps = [SELECT id,Amount
FROM Opportunity
WHERE id in :oppsList];
//convert that to an opportunity
Opportunity newOpp = opps.get(0);
//change a value on it
newOpp.Amount = 800;
//instantiate a new list
List <Opportunity> returnOpps = new List <Opportunity> ();
//add that modified opp to the new list
returnOpps.add(newOpp);
//send the new list of just one opp back to the Flow
return returnOpps;
}
}
Learn More
Official Documentation on Invocable Methods.
This is extremely useful, thank you so much! I find it weird that when I wish to send a collection, the matching datatype in Apex has to be a collection of collections. And it is not exactly properly documented by Salesforce anywhere, or at least I could not find it. Thanks for this.
Exactly! I write most of my posts with the documentation could use some additional help 🙂
Thank you ! This is very helpful to write an APEX action. How can we write the test class for the same? Can you please show one example of Test class where we are passing a collection of string (emails) to the APEX class.
Sure. I will see if I can get that within a few days
Thank you so much for your reply. Though would really help me . Thank you again 🙂
Priyesh, what are you doing with the emails? I think the test would trigger the flow, and then check for the results just like anything else. I don’t think it really matters what happens in the middle regarding getting the emails into the class.
I’m sending HTML email to contacts using APEX action. I’m getting a list of list of email addresses into the APEX action as variable. Based on contacts geo location I’m doing some template selection and Org wide address selection for sending email in the apex class. I’m not able to write a test class for the same. How do I pass test data(test email addresses) to List of List collection variable in flow in test class?
If it’s a record triggered flow, you create test data in your test, and launch the flow from your test. Then you confirm the results. If you are testing just the apex part, you could create a list of list of strings like this: listofStrings = new List {‘jon@bob.com’,’jonny@bobby.com’};> stringListList = new List>();
//declare new list of strings
List
//declare a new list of lists of strings
List
//add the list Strings to the list of lists
stringListList.add(listOfStrings);
Hello – Something to consider is that (unlike Flow) Apex requires that you bulkify the entry of data into an Apex method. That’s why if you want to process ONE object (eg, an Account record) in an Apex method, it MUST ACCEPT a LIST (eg, a list of Account records). Imagine loading 1000 objects into Salesforce using Data Loader and they ALL call your method. The system will process them in batches of 200 through your method — and that’s why your method MUST accept a list. This list will hold those 200 objects. Likewise, if your method is designed to receive and process a list (eg, a list of Account records) then your method MUST ACCEPT a List of Lists (so that it can batch process 200 lists at a time). As far as sending data back to Flow, you must always send back a list (or a List of lists). However, if (for example) you send a list of Accounts back WITH ONLY ONE record in it, and you assign it to a SINGLE RECORD variable back in flow — it will accept it. But if you try to send a list back that contains TWO or MORE records (objects) the flow will likely fail if you try to assign it to a single record variable. Anyway, I’m pretty new to this myself, so I’m glad to have found your blog! Thanks!
Thanks so much for sharing! Is this contradicting anything I said? Or do you suggest I update it with this info? This stuff is hard to keep track of!
Hi Jessie — I think the article should be updated to state up front that an Apex invocable method must always accept a list and return a list (unless the return type is void). This is well documented in Salesforce here, under the heading “Inputs and Outputs”: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_InvocableMethod.htm
You could still use the table to reflect what the input/output lists would look like based on what data you were passing between the method and Flow (eg, if you passed a collection (list) of Account records to the method, your method must receive a list of lists). As far as the bulkification of data entering a method, this is not unlike receiving the Trigger.new in an Apex trigger. Even if one record is being processed, you will always receive a list of records — a list that you then must process in your code (even if it only contains one item).
Thank you! I have made that update. This is a really good point regarding triggers. I appreciate your help.
Going off bulkification – these examples will not work for situations in bulk if they’re on a record-triggered flow
The reason you accept a List of Ids in your method even though in Flow you only pass one Id is because of bulkification as the other comment mentioned.
If this is a record-triggered flow – there’s a different flow interview for different records in the same transaction. Yet, the invocable apex method is only called once (all Ids get passed in).
The key in that bulk scenario is that your invocable method needs to return the same number of outputs as inputs. Ex. 6 contact Ids in input, expects a list of 6 lists. One list for each input. Even if the query returns null, you’d have to put an empty list to signify that Id returned no records.
Thank you!! I will try to revise the post to reflect this.