How to Build a Complex Validation Rule for Multi-Select Picklist in Salesforce Flow

Allow users to select additional values but prevent removal of existing selections ("Pick but no un-pick" validation).

Multi-Select Picklists can be tricky to handle when it comes to validations. In this article I will show how a Flow with the “Custom Error Message” element can be used for a validation logic that can be way more complex than with the “classic” validation rules.

The example requirement is to create a validation message upon record save for a multi-select picklist field “Product Interest” as follows:

  • We want to allow users to choose additional values, e.g. add “Dairy Products โ€” Butter” to the “chosen” list.
  • But we want to prevent users from un-selecting values, e.g. remove “Fruits โ€” Apples” from the “chosen” list.
Multi-select picklist field Product Interest

Multi-select picklist field “Product Interest”.

The Data Model

The example will be shown with a multi-select picklist custom field on the Lead object:

1
2
3
4
API Name:         ProductInterest__c
Label:            Product Interest
Data Type:        Picklist (Multi-Select)
Global Value Set: Product Groups

The global value set “Product Groups”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 Values                    API Name
------------------------- ---------------
 Dairy Products - Butter   Dairy.03
 Dairy Products - Cheese   Dairy.04
 Dairy Products - Milk     Dairy.01
 Dairy Products - Yogurt   Dairy.02
 Fruits - Apples           Fruits.01
 Fruits - Bananas          Fruits.02
 Fruits - Oranges          Fruits.04
 Fruits - Strawberries     Fruits.03
 Sweets - Chocolate bars   Sweets.01
 Sweets - Cookies          Sweets.02
 Vegetables - Broccoli     Vegetables.02
 Vegetables - Carrots      Vegetables.01
 Vegetables - Spinach      Vegetables.03

The Flow

See below the preview of the flow we are about to build:

The Validation Flow

The Validation Flow

Start Element

Create a “Record-Triggered Flow” that runs before the record is saved. (Will also work with an after-save flow if this better fits your use case.)

  • Object: Lead (or any other standard or custom object)
  • Trigger the Flow When: “A record is updated”
  • Condition Requirements: Field “Product Interest” Is Changed = True
Start Element configuration

Start Element configuration

Helper Constant

Define a Constant as follows for clearer reference inside the Flow:

Section Constants

Section “Constants”

Define the semicolon as a constant PicklistValueDelimiter

Define the semicolon “;” as a constant PicklistValueDelimiter

Helper Variables

We will need two helper text variables:

Section Variables

Section “Variables”

defined as follows:

Text Variable: CurrentPicklistValues

Text Variable: CurrentPicklistValues

Text Variable: PriorPicklistValues

Text Variable: PriorPicklistValues

Get the Object Definition

A Get Records element to get the object “Entity Definition” with the definition of the object we are working on, here: Lead.

getObjectDefinition

getObjectDefinition

If you want to do this with a custom object, don’t forget to add “__c” to the API Name, e.g. “MyCustomObject__c”.

Get the Field Definition

A Get Records element to query the object “Field Definition” for our multi-select picklist field “ProductInterest__c” using the “Durable Id” we’ve got in the previous step:

getFieldDefinition

getFieldDefinition

Get the Active Picklist Values

A Get Records element to query the object “Picklist Value Info” for all currently active picklist values for this field:

getActivePicklistValues

getActivePicklistValues

Notes:

  • Make sure the setting under “How Many Records to Store” reads “All records” (unlike the other Get Records elements before).
  • This will only return active picklist values. There is no way to query for inactive values with this method.
  • In case you are using Record Types, also be aware: This will only return picklist values for the current record type of the current record.

Convert the Picklist Field Values to Delimiter-separated Text Variables

An Assignment Element as follows:

convertPicklistToText

convertPicklistToText

En detail, this reads:

1
2
3
4
{!CurrentPicklistValues}    Equals    {!$Record.ProductInterest__c}
{!PriorPicklistValues}      Equals    {!$Record__Prior.ProductInterest__c}
{!CurrentPicklistValues}    Add       {!PicklistValueDelimiter}
{!PriorPicklistValues}      Add       {!PicklistValueDelimiter}

Loop Through the Active Picklist Values

Now we loop through all the values as per record collection returned by getActivePicklistValues:

loopPicklistValues

loopPicklistValues

For each known picklist value, we check whether it was present in the PRIOR value but is missing in the CURRENT value. If such case occurs, we know that we need to raise the validation error message towards the user:

Check Has it been removed

Check_Has_it_been_removed

The condition logic reads “1 AND NOT 2” with the following conditions:

1
2
{!PriorPicklistValues}   Contains {!LoopPicklistValueWithTrailingDelimiter}
{!CurrentPicklistValues} Contains {!LoopPicklistValueWithTrailingDelimiter}

where “LoopPicklistValueWithTrailingDelimiter” is a helper text formula defined as follows:

1
{!loopPicklistValues.Value} + {!PicklistValueDelimiter}

Validation Error Message

Upon the first occurrence of such case, we use the “Custom Error Message” element to raise the validation error to the user and stop the flow:

ValidationMessage

ValidationMessage

Show Case

Now let’s run a test: Go to the example Lead, click “Edit” and play around with the multi-select picklist fields. Once you try to save the record after un-choosing a picklist value, the validation error message will be shown:

Test Run

Test Run

Conclusion and Considerations

  • The “Custom Error Message” flow element has the same effect as a traditional validation rule: Once fired, the save transaction will fail and the user (in case of a UI transaction) or the caller of the interface (in case of an API/Dataloader transaction) will get the custom error message.
  • Record-triggered flows are way more flexible for implementing complex validation logic compared to the formula field-style “classic” validation rules.
  • Always be aware of the “order of execution”: In our example, we’ve used a before-save flow as this gives best performance in such scenario and we know that, by our design, no other logic will touch the picklist values on our record. But you need to assess whether other parts of other flows might update the picklist fields and you need to put the validation elsewhere in the order of execution, e.g. by setting “Trigger Order” in the flow properties and/or switching to an after-save flow.
  • The “Get Records” on “Picklist Value Info” only ever will see and return active picklist values for the given record type. Hence, we cannot identify the (very special) case where the user decides to un-choose a value that is a now-inactive picklist value: We don’t know about it, hence we won’t check for it in our loop.

This article has been written as per Summer ‘25 release.

For more information about handling picklist values in Flow also see the article “How to get Picklist Labels in Salesforce Flow”.

Liked this article? Clap or comment on its Medium version.
Follow me on Medium or LinkedIn to get notified about new articles.
New to Medium? It's free, no paid membership needed โ€” here's how it works.