I received an INVALID_TYPE_ON_FIELD_IN_RECORD
/ value not of required type
error that made no sense when editing a Salesforce flow. Turns out it's a known error. Here's how I got around it.
Status Quo
Within an org I administer, one department's salespeople occasionally need to add new Contact records to Salesforce in a very strict fashion.
From their perspective, they have very little to say about the new Contact besides name, email, and which product the Contact is interested in buying.
However, from the operations manager's perspective, there's all sorts of "extra" data entry that needs to happen with custom objects, Opportunity (to sell the product in question to that Contact), etc.
What we decided to do was lock down Contact creation so that sales-team users couldn't do it, but give them a "system mode" data entry screen that would do it for them (as well as all the other auxiliary work).
I built the user interface to this "internal form" as a Screen Flow (to save myself Visualforce or Lightning Component coding hassle) and delegated the "system-mode data entry" work to Invocable Apex.
The architecture of my Flow was that:
- The Screen collected name, email, and product.
- The Flow then set
FirstName
,LastName
, andEmail
on a Contact-typed Flow Variable. - The Flow passed this Flow Variable over to some Invocable Apex.
- The Invocable Apex actually does the DML
INSERT
of the Contact in system mode.
Change Request
The operations manager said it'd be nice to have salespeople also enter a value for LeadSource when creating a new Contact, and asked me to add that to the form's user interface.
It sounded easy-peasy. Two steps:
- Collect "lead source" in the screen.
- Have the Flow set the Contact-typed flow variable's
LeadSource
in addition toFirstName
,LastName
, andEmail
before passing the flow variable to invocable Apex.
Error
Unfortunately, I ran into this issue (thanks, for saving me so much headache by blogging all your Tweets for easy search engine findability, Jason!):
"Flow that passes SObject with Picklist field as input to Invocable Apex will fail with INVALID_TYPE_ON_FIELD_IN_RECORD error"
The original "known issue" on Salesforce's web site that Jason linked to no longer works, but his tweet let me know what was going on when I got the following error:
Error element Create_new_Contact (FlowActionCall).
An Apex error occurred: System.DmlException: Insert failed. First exception on row 0; first error: INVALID_TYPE_ON_FIELD_IN_RECORD, Lead Source: value not of required type: Blah Blah Blah: [LeadSource]
The Fix
The heart of what I did is to make it so I'm technically not passing my Invocable Apex Method a Contact
that has a value populated on a picklist-typed field anymore (which is what triggers the nonsensical error).
Instead, I pass it two parameters -- a Contact
and a String
-- then combine them inside the Apex.
I altered the input parameter signature of my Invocable Apex Method.
- Formerly, it required a single Contact input parameter
- (Note to coders: remember that Invocable-Apex-land, that's a
List<Contact>
parameter you need to[0]
. Don't ask me why.)
- (Note to coders: remember that Invocable-Apex-land, that's a
- Now it requires a more complex set of parameters.
- It still takes the Contact input parameter (into which my Apex Action in Flow passes the Contact-typed Flow Variable), but
- Now it also takes a String-typed input parameter (into which my Apex Action in Flow passes the value that an end user chose for "Lead Source" in the Flow's Screen).
- Personally, I used Apex-defined data types to "clump" the two input parameters together into a single parameter, but I'm pretty sure I also could have done it with a 2-parameter method that took a
List<Contact>
and aList<String>
.
- Personally, I used Apex-defined data types to "clump" the two input parameters together into a single parameter, but I'm pretty sure I also could have done it with a 2-parameter method that took a
Then, within the Apex, before doing INSERT
againt the SObject
representing the Contact
passed to it, it sets LeadSource
on that SObject
to be the other value I passed into Apex from the flow.
- Note that what I don't do is try to set the value of
LeadSource
on the Flow Variable before it makes its way into the Apex.- The Flow itself still just sets
FirstName
,LastName
, and
I also had to:
- Change my Apex unit test and confirm it still passed after changing my invocable method signature.
- Change my Flow's invocation of the invocable method to use the new input parameters (Flow's UI made it really easy -- thanks, Salesforce Flow team!)
Want To See Code?
For code-heads, here's the old Apex:
public class CreateContactInvocable {
@InvocableMethod(label='Create new Contact' description='Returns a Contact ID if the work was done correctly, NULL if not.')
public static List<Id> doContactCreate(List<CreateContactInvocable> inputContactDetailsList) {
Id cid = NULL;
if (inputContactDetailsList.size() > 0) {
Contact c = inputContactDetailsList[0];
if (c != NULL) {
if (c.Id != NULL) {
cid = c.Id;
}
else if (c.LastName != NULL && c.LastName != '' && c.FirstName != NULL && c.FirstName != '' && c.Email != NULL && c.Email != '') {
INSERT c;
cid = c.Id;
}
}
}
return new List<Id>{cid};
}
}
And here's the new Apex:
public class CreateContactInvocable {
@AuraEnabled @InvocableVariable public Contact the_contact;
@AuraEnabled @InvocableVariable public String the_lead_source;
@InvocableMethod(label='Create new Contact' description='Returns a Contact ID if the work was done correctly, NULL if not.')
public static List<Id> doContactCreate(List<CreateContactInvocable> inputContactDetailsList) {
Id cid = NULL;
if (inputContactDetailsList.size() > 0) {
Contact c = inputContactDetailsList[0].the_contact;
String lead_source = inputContactDetailsList[0].the_lead_source;
if (c != NULL) {
if (c.Id != NULL) {
cid = c.Id;
}
else if (c.LastName != NULL && c.LastName != '' && c.FirstName != NULL && c.FirstName != '' && c.Email != NULL && c.Email != '') {
c.LeadSource = lead_source;
INSERT c;
cid = c.Id;
}
}
}
return new List<Id>{cid};
}
}
The additions are:
-
@AuraEnabled @InvocableVariable public Contact the_contact;
-- this turns my class into an "Apex-Defined Data Type" -
@AuraEnabled @InvocableVariable public String the_lead_source;
-- this turns my class into an "Apex-Defined Data Type" - Altering
c
to beinputContactDetailsList[0].the_contact
instead ofinputContactDetailsList
String lead_source = inputContactDetailsList[0].the_lead_source;
-
c.LeadSource = lead_source;
right beforeINSERT c;
(this is the heart of what I did)