Hi there! 🙋🏻♂️
Are you confused why we are talking about an Apex action that call another flow when we already have a ‘Subflow’ element in flows? Well, allow me…
If you’ve already started playing around with Record Triggered or Platform Event triggered flows, you might have noticed that the Subflow element is missing for them. Yes, so for the time being, we cannot call subflows in the above specified flow types. Quite a bummer!
Not to worry though! Apex actions to our rescue again.
How does it work?
So I’ve built an Apex action that enables you to call a flow as a ‘subflow’.
The action takes follow values as input/output:
Parameter | Use for Input | Use for Output | Description |
---|---|---|---|
Flow API Name | ✅ | API name of the flow. | |
Flow Namespace | ✅ | Namespace of the flow. Leave the input blank if the flow does not have a namespace. | |
JSON of Input Variables | ✅ | JSON string of flow input variables and their corresponding values. Ex: If your flow has two input variables; varText(text type) and varNumber(number type), then the JSON should look like this. { “varText” : “Text Input”, “varNumber” : 3000 } Store the JSON string in a variable using a Text type variable or a Text Template(with View as Plain Text) and pass that variable in the action input. |
This action can prove to be very handy for calling for error handling flows and for many more use cases.
You can find the link for the unmanaged package and source code here:
https://github.com/forcePanda/Flow-Repo/tree/master/Apex%20Actions/Call%20Subflow
Quick Demo? Of course!
Hope you found this one useful! Catch you in the next one! ✌
Thank you for being an awesome reader! Subscribe to this blog for receiving all the latest updates straight to your inbox. 🙂
Pingback: Winter ’21 – Introducing the Before Delete Record-Triggered Flow – Jenwlee's Salesforce Blog
Pingback: Winter ’21 – Introducing the Before Delete Record-Triggered Flow – Jenwlee's Salesforce Blog - Salesforce News & Tutorials
Thanks for the work and the solution! Quick questios, does the Actoiun allow objects to be sent? For example, could I send the following to a subflow:
{
“varOpportunity”:”{!Record}”
}
Thanks for your help!
LikeLike
I haven’t tried it. Ideally speaking it should, but I’ve my doubts.
Alternate would be to pass the record Id and query the record again in subflow, but that costs you an extra query unfortunately.
LikeLike
Well, I attempted it. I might be doing it wrong, but I did it exactly as above, and then also tried it without quotes (which didn’t work at all). Got this error when I sent with quotes:
“Error Occurred: An Apex error occurred: System.FlowException: Unable to set value for variable ‘varAccount’. Flow encountered an error when processing and converting between data types. Please check the flow and ensure all data types are matched correctly.”
The Apex Action did call the subflow, but nothing got passed over. I’ll try it with just the recordId and see what we can see. Thanks again for your work.
LikeLike
Can you share how your text template variable looks?
LikeLike
{
“varAccount”:”{!Record}”
}
varAccount is a record variable in the subflow set to input
LikeLike
Can you try by removing the double quotes around the {!Record} variable?
So something like:
{
… : {!Record}
}
Enclosing the variable in the double quotes makes it string(text) type variable. By removing the quotes we are passing the record as an Object.
I’m not too hopeful but it’s worth trying. 🙂
LikeLike
Here is the error:
Error element call_sub_flow (FlowActionCall).
An Apex error occurred: System.JSONException: Invalid numeric value: Leading zeroes not allowed at [line:2, column:15]
Here is the point of the erro (xed out the ID after the 001):
FLOWACTION_CALLSUBFLOWHANDLER (APEX): call_sub_flow
Inputs:
flowApiName = updatetest
inputVariablesJSON = {!textJSON} ({ “varAccount”:Account (001xxxxxxxxxxxxxxx) })
It worked perfectly when passing in the recordId. Still, this really helps me out. This way I don’t have to refactor an existing set of sub flows in my automation. I’d hate to reinvent the wheel.
LikeLike
Yeah, I can understand when you’ve to spend time doing “not so productive” stuff.
But I guess for now, you’ll have to manage with passing the record Id. Because passing the record doesn’t seem to work.
I’ve an idea but it’d require you to create another Invocable method(Apex action).
JFYI, the idea would be to get serialized output of record from the Apex action and pass that in the varAccount.
I’ve a feeling that it might work.
But if that also doesn’t work then I don’t know what will.😅
LikeLike
Is there a way to add a carriage return to the variables. I am trying to set a case description and based upon what was updated on the account the description can be one line or multiple paragraphs.
What I have so far:
{
“varCaseDescription” : “The name was changed from {!$Record__Prior.Name} to {!$Record.Name}
}
What I want – 4 separate lines so that it is easier to read on the case.
{
“varCaseDescription” : “The name was changed from
{!$Record__Prior.Name}
to
{!$Record.Name}”
}
LikeLike
Hi Justin,
Have you tried using \n for line breaks?
So,
{
“varCaseDescription” : “The name was changed from
{!$Record__Prior.Name}
to
{!$Record.Name}”
}
should look like:
“varCaseDescription” : “The name was changed from \n{!$Record__Prior.Name} \nto \n{!$Record.Name}”
LikeLike
That worked great, thanks for the fast reply
LikeLike
Hello, is there a way to pass a null or blank field text?
{
“input_text” : {!input_reemplaza}
}
If {!input_reemplaza} is a null field, i get an error.
LikeLike
What is the data type of input_reemplaza variable, text or something else?
LikeLike
Hello,
I’m new to apex so I’m a little lost. I’m trying to use this to call a flow we created that is a 1 minute pause. So we don’t need to pass any info int the “Sub Flow”. Only need it to trigger the other flow. We are using this pause in a loop to wait for a field to be set then it will break the loop.
I filled in the Flow API Name in the action but I’m not sure what to write into the JSON section. Any help would be great.
Thank you
LikeLike
Hi David,
Have you tried leaving it blank?
You can also try setting it to null(GlobalConstant.EmptyString).
LikeLike
Hi ForcePanda,
Thanks for the reply. The scenarios I tested and the results below. This error is triggered when I go to save a file that triggers the record triggered flow I created.
If I don’t include using the JSON parameter I get this error:
Error Occurred: An Apex error occurred: System.NullPointerException: null input to JSON parser
If I include the JSON parameter and use the Null value in the variable I get this error:
An Apex error occurred: System.JSONException: no content to map to Object due to end of input
If I include the JSON parameter and use the blank value in the variable I get this error:
Error Occurred: An Apex error occurred: System.JSONException: no content to map to Object due to end of input
LikeLike
Oh yes, silly me!
You can simply set inputVariablesJSON parameter to this:
{}
This should work.
LikeLike
Yes that did work! Thank you very much!
Now I have a new problem though haha. I am trying to use a sub flow to trigger an auto launched flow that has a pause in it. The flow I’m calling it from is a record triggered flow and I was hoping I could use that pause but it just fires that flow and keeps going through the logic… silly me. Do you know how to use a pause function inside of a record triggered flow?
LikeLike
What’s your use case?
LikeLike
So we ran into an issue recently where some emails were being sent out blank because one of the fields it is referencing was not set. There is a process outside of SFDC that sets that field and sometimes it’s delayed and sets the field after the email is triggered. My idea is the put the current logic into a flow and wait until that field is set and then send the emails. So currently the flow can take 2 paths. One is where that field is set and it looks at a subscription name to determine what email to send. The other path is where the one field is not set and it goes through some loop type logic that checks if that field is set over and over for about 10 minutes every X amount of minutes. It will either trigger an error email after 10 minutes of it not being set or it gets set during that time and continues on path 1. My idea was to use a sub flow to pause and then continue the current flow once that pause is complete. That is not the case and it just triggers that flow and moves on to the next action in the logic.
LikeLike
Any reason for not using Scheduled paths for this?
LikeLike
No actually, I have never used that before so I didn’t think to try it. It looks pretty straight forward. I assume I can connect all the logic to the scheduled path and set the schedule to a 10 minuet delay?
LikeLike
Unfortunately, you can only do hours and days.
One solution that I can think of is using a scheduled path to fire it after 0 hours, and then call subflow which has a pause element for 10 mins.
LikeLike
Ok. I’m unsure how to actually pause the flow though with the subflow. The way it’s setup now is that it does call the sub flow for the pause but it’s then continuing to the nest step before the actual pause is over. I would like it to complete the pause sub flow before it continues to the next piece of logic in the flow
LikeLike
Can you post a snapshot of your flow screen?
LikeLike
I sure would but I’m not sure how. I don’t see an attachment button and I’ve also tried copy and pasting but to avail.
LikeLike
Maybe put it on cloud and share the link?
LikeLike
Great idea, why didn’t I think of that! https://drive.google.com/file/d/1iDgvh7mhObSBIhnFt192voQCd0ws8Ldw/view?usp=sharing
LikeLike
I’m just wondering don’t you create a record triggered flow that fires when the field’s value get set and then send the email?
LikeLike
We only want this to run when the record is created. So if that field is set after this record is created we are in the same boat as before.
I may be overlooking what you explaining though. From what I gather the logic you are presenting is when a record is created and that field is set.
LikeLike
As you said, the field gets set by an external system.
So do you mean you want to send the email only the first time when that field is set and not when it’s updated at some point later?
LikeLike
I only want to send the email when the DB Type field is set/not null and certain subscription rate plans are created. So yes, the email should only be sent once at the first time that Subscription is sold to them.
LikeLike
In that case, I think a more simpler approach here would be to create a checkbox to keep track of if the email is sent or not.
Then create a flow which fires when the DB Type is updated as not null and checkbox is false(along with other conditions). In the flow, send the email and update the checkbox.
LikeLike
Great idea, thank you very much!
LikeLike
Hi Narendra,
I think the shared link for the unmanaged package and source code is not updated. It’s having only one Flow(name: Dummy_Flow) and there is no sub-flow in that package. Could you please check and provide the updated package URL.
https://github.com/forcePanda/Flow-Repo/tree/master/Apex%20Actions/Call%20Subflow
Thanks in advance!!
LikeLike
Hi,
I can see the flow in the package.
LikeLike
This is just what I needed! Thank you! Do you have an example of how an output variable in the subflow can be passed back into the main Flow from the Call Subflow action? It looks like you allude to this a couple of times, but I’m not sure how to set it up. I tried adding the output variable in the text template for the JSON parameter in the Apex Action, but this didn’t seem to work (the first variable is an input parameter for my subflow, and the second one is the output parameter). Here is my text template:
{
“RecordType_Id” : “{!$Record.RecordTypeId}”,
“RecordType_ApiName” : “{!RecordType_ApiName}”
}
My subflow is pretty simple — it’s just retrieving the RecordType Developer Name corresponding with the RecordType Id from the main Flow. The subflow is running in system context without sharing, so that the main Flow can run in user context with sharing (and so that I can avoid hard coding record type IDs in my Flows).
Thanks in advance!
LikeLike
Output variable handling is a bit complicated to implement. What makes it complicated is there is no good way to make it generic. I’ve some ideas but can’t say anything until tried.
The current component doesn’t handle output variables at all. So it’s no good if you want to use it to get some output from the subflow.
LikeLike
Ok — thanks for your response!
LikeLike
I’m trying to send a list of Id’s through and it isn’t working for me. I’m not getting any error when debugging though and the subflow does not get called.
My text template for the JSON is this:
{ “InOppIds” : [“{!$Record.Id}”] }
And it turns into valid JSON:
{ “InOppIds” : [“0062h00000PLr7JAAT”] }
LikeLike
I updated it to try with a single Id and I’m getting the same result. Anything I could be missing?
Changed JSON to:
{ “InOppId” : “0062h00000PLr7JAAT” }
LikeLike
Is the variable marked as Available for Input?
LikeLike
It is marked as available for input. I have a fault path set up on the subflow action and the flow isn’t going down that path. I’m logging as soon as the subflow begins as well and it isn’t logging anything there. So I’m not sure what else to do.
LikeLike
Can you share a snapshot of how you’ve defined the text template variable containing the JSON?
LikeLike
Here is the body of my text template.
{ “InOppId” : “{!$Record.Id}” }
When I print it out it looks right.
{ “InOppId” : “0062h00000PLr7JAAT” }
Here is the error thrown running the flow
An Apex error occurred: System.FlowException: An unhandled fault has occurred in this flow An unhandled fault has occurred while processing the flow. Please contact your system administrator for more information.
And in the debug log here is the real error is…
It’s actually one of my debug logs causing an error. So I’m all good. I don’t know what update fixed it but it’s working now.
LikeLike
Haha great! 🙂
LikeLike
We were having some issues with the JSON variable for recordid not being passed into the subflows – this was caused by the sublows’ API being in older versions. Just a tip in case anyone encountered the same issue. 🙂
LikeLike
Getting the following error when I use {} as input
An Apex error occurred: System.JSONException: Unexpected character (‘
LikeLike
What exactly are you trying to achieve?
LikeLike
Hey Forcepanda,
Love the concept but one has to watch out for Limits! When this runs on a single record save, it works great. However, when one is trying to insert or update say 200 records, it bombs out if the sub-flow being invoked has a Get Records or Create, Update, or Delete element in it. That’s because the code instantiates each sublow and then runs the sub-flow for each record one after the other. This effectively calls the sub-flows serially and isn’t bulkified.
This is caused by the Salesforce Interview apex class not offering a bulk way to start records. I was recently looking at Douglas Ayer’s “Mass Action Scheduler” and it bulk starts flows by calling the Salesforce Action’s API. It would be nice to have that capability too here but suspect you’d run into the callouts aren’t allow after DML issue.
Have you run into this before and resolved it?
LikeLike
Yeah, it’s a big issue. Since Apex currently doesn’t support launching Flow interviews in bulk. There should be an idea for this on community.
There is no nice/clean way to do it, AFAIK.
Can you elaborate on your use case? I might be able to suggest something.
LikeLike
It’s actually a continuation of my issues above. I was trying to pass an array of Id’s in, but Flow Triggers only get 1 record at a time. Since I can’t pass the whole record from the trigger, I’m passing the Id only. Then pausing and doing 1 query, 1 assignment, 1 update, and I’m hitting 101 SOQL query limits.
LikeLike
Jake, the action might not allow you to pass a whole record but you can pass specific values that you want to use in your subflow instead of doing a query.
LikeLike
The only solution I can think of is to inline the sub-flow into the “master / calling” which defeats the purpose for reuse / maintenance. One could also use Apex instead but that requires developer skills.
LikeLike
Yeah, it’s best to decrease the batch size if you’re using this action in your record triggered flows.
Just outta curiosity, are you doing this in a after save flow or before save flow?
LikeLike
The Flow record-triggered batch size is 200 records. If 600 records are updated, 3 batches of 200 record-triggered flow interviews run one after the other. Unfortunately, that can’t be controlled. One would have to control the batch size on whatever is inserting, updating, or deleting records en mass.
An after-save flow was used because an Apex action can’t be invoked from a before-save flow.
LikeLike
Yes, what I meant was to decrease the batch size if you’re doing a mass update/insert.
P.S. You should simply use PB instead of After Save flow as there is not any significant performance difference. PBs under the hood are just flows.
It’s before save flows that are performant, understandably so because they don’t do an explicit DML.
LikeLike
This discussion has been great. Got me thinking about all the different declarative alternatives so I wrote this blog post about it: https://metillium.com/2021/07/salesforce-sub-flow-declarative-invocation-alternatives/
Before-save flows are much more performant. I did some record automation benchmarking a while back on that too: https://metillium.com/2020/10/salesforce-record-automation-benchmarking/
LikeLike
Pingback: Salesforce Sub-Flow Declarative Invocation Alternatives
I’m getting this error. I’m trying to dynamically call another flow by passing the API Name and additional values into a subflow. My concern is that I’m calling A Screen Flow from a Screen Flow and that might be causing the issue. Can you provide an example of passing a text id into the subflow via variable from the parent. Of course, don’t bother if the issue is screen flow to screen flow.
Error Occurred: An Apex error occurred: System.JSONException: Unexpected character (‘“’ (code 8220 / 0x201c)): was expecting double-quote to start field name at [line:2, column:2]
LikeLike
Why use this action if you want to call a subflow via a screen flow? This functionality is available out of the box for Screen flows. Just simply use Subflow element. No?
LikeLike
You can’t call a screen flow from a screen flow. The out of the box subflow element can only launch Autolaunched Flows.
LikeLike