---
title: "The call was logged. The nurture tag silently never happened."
description: "A subflow tags a builder for nurture when the call outcome is Existing Installer. The first formula would not deploy because it touched a multi-select picklist. The second deployed, ran without a fault, updated every other field in the same assignment, and wrote null. How it was found, why getVariableValue could not see it, and the nested IF that fixed it."
source: "https://noconaconsulting.com/blog/flow-formula-null-multi-select-picklist/"
---

# The call was logged. The nurture tag silently never happened.

_Build log · Doorvana · Garage door manufacturing & service_

A subflow tags a builder for nurture when the call outcome is Existing Installer. The first formula would not deploy because it touched a multi-select picklist. The second deployed, ran without a fault, updated every other field in the same assignment, and wrote null. How it was found, why getVariableValue could not see it, and the nested IF that fixed it.

Nocona Consulting · October 15, 2026 · 7 min read

The builder said “we already have an installer, call me after the new year”. The rep logged the call. The follow-up moved two months out, the call showed up in the activity history, and the prospect was supposed to land on the list of builders worth nurturing.

The tag that puts him there was never written. No error, no fault email, no red banner. Every other field in the same step updated exactly as designed. One field got `null`, and because it was already empty, `null` looked like nothing at all.

This is how that was found, and why it took four attempts to get a one-line idea right.

## What the board is

At [Doorvana](https://noconaconsulting.com/work/), new-construction sales starts with cold outreach to home builders. The prospecting board is built on `Lead`: builder prospects sit on the same `Service_Lead` record type as everyone else and are split out by `Customer_Type__c = Builder`. A **Log Outreach** quick action launches a screen flow; the screen flow hands its inputs to an autolaunched subflow, `Log_Outreach_Apply`, which does all the work. The split is deliberate — the rules can be exercised headlessly from Apex, and a future telephony or import path can call the same subflow.

`Log_Outreach_Apply` creates a completed `Task` for the history and stamps the current state onto the Lead, because list views cannot filter on `Task` fields. Every value is pre-decided by a formula resource, and one Assignment element writes them all.

One rule in it: when the outcome is **Existing Installer**, add `Existing Vendor` to the multi-select picklist `Nurturing_Reason__c`, without dropping any reason already there. The **Prospects – Nurture** list view filters on that field with `includes`.

## Attempt one: the deploy refused it

The natural formula concatenates the field directly. The deploy said no:

```plaintext
The formula expression is invalid: Field Get_Lead is a multi-select picklist field.
Multi-select picklist fields are only supported in certain functions.
```

That one is fair and well documented. In a flow formula a multi-select picklist field is only usable inside a handful of functions — `INCLUDES`, `ISBLANK`, `ISNULL` and a few others. No `&`, no `CONTAINS`. The standard workaround is to copy the field into a Text variable first and build the formula on the variable:

force-app/main/default/flows/Log\_Outreach\_Apply.flow-meta.xml

```xml
<assignments>
    <name>Copy_Nurturing_Reason</name>
    <label>Copy Nurturing Reason</label>
    <assignmentItems>
        <assignToReference>v_NurturingReason</assignToReference>
        <operator>Assign</operator>
        <value>
            <elementReference>Get_Lead.Nurturing_Reason__c</elementReference>
        </value>
    </assignmentItems>
    <connector>
        <targetReference>Stamp_Lead</targetReference>
    </connector>
</assignments>
```

With the formula reading `v_NurturingReason` instead of the field, it deployed cleanly.

## Attempt two: it deployed, it ran, it wrote null

The acceptance script drives the subflow from anonymous Apex with `Flow.Interview.createInterview('Log_Outreach_Apply', inputs).start()`, then reloads the Lead and checks each field. Five fabricated builder cases, 35 checks. One failed:

```plaintext
FAIL C2 tagged Nurturing_Reason__c Existing Vendor => null
```

Case 2 is the existing-installer builder. In the same run, on the same record, through the same Assignment element, `Last_Outreach_Outcome__c` was `Existing Installer`, the follow-up date was two months out, the status had moved to `Working - Engaged`. Only the nurture tag was missing.

A null formula is not a flow fault

A formula resource that evaluates to `null` does not fault the interview. The Assignment assigns `null`, Update Records saves it, and the flow reports success. If the field was already blank — as it is on every new prospect — the record looks untouched. Nothing short of reading the field back will tell you.

The first thing to rule out was the field itself. A two-line anonymous Apex probe inside a savepoint set `Nurturing_Reason__c = 'Existing Vendor'` directly, read it back, and rolled back. The value stuck. So the field, the value set and the permissions were fine; the formula was handing the assignment nothing.

## Attempt three: you cannot read what is not an output

The obvious next step is to ask the interview what the formula evaluated to. `Flow.Interview` has a method for exactly that:

```apex
Flow.Interview fi = Flow.Interview.createInterview('Log_Outreach_Apply', inputs);
fi.start();
System.debug(fi.getVariableValue('v_NurturingReason'));
```

It returned `null` — which was useless, because **`getVariableValue` only returns variables marked Available for output**. A private variable or a formula resource comes back `null` whether it holds a value or not. The method cannot distinguish “empty” from “you are not allowed to see this”, and it does not say which.

getVariableValue reads outputs only

`Flow.Interview.getVariableValue` returns `null` for any variable not marked Available for output, regardless of its actual value. To inspect an intermediate value from Apex, make a debug copy of the flow, assign the formula to an output variable, and read that. Do not flip the real flow’s variables to output just to debug it.

A debug copy of the subflow did exactly that: the formula’s result assigned to a new output variable, nothing else changed. That settled it. On the path where the Lead’s existing `Nurturing_Reason__c` was empty, **the formula itself evaluated to `null`.**

## Attempt four: BLANKVALUE did not help, nesting did

The first reasonable fix is to make sure the variable is never null going in — wrap the variable in `BLANKVALUE()`. That deployed and changed nothing. The result was still `null`.

What worked was restructuring the formula so `ISBLANK` is tested first, in its own `IF`, and `CONTAINS` is only reached when the value is already known to be non-blank:

force-app/main/default/flows/Log\_Outreach\_Apply.flow-meta.xml

```xml
<formulas>
    <name>f_NurturingReason</name>
    <dataType>String</dataType>
    <expression>IF({!outcome} &lt;&gt; &quot;Existing Installer&quot;, {!v_NurturingReason},
  IF(ISBLANK({!v_NurturingReason}), &quot;Existing Vendor&quot;,
    IF(CONTAINS({!v_NurturingReason}, &quot;Existing Vendor&quot;), {!v_NurturingReason}, {!v_NurturingReason} &amp; &quot;;Existing Vendor&quot;)
  )
)</expression>
</formulas>
```

Read top to bottom it is three questions, each only asked if the previous one let it through:

1.  Is this an existing-installer outcome? If not, keep whatever is there.
2.  Is the current value blank? Then the answer is just `Existing Vendor`.
3.  Only now — with a value known to exist — does it already contain the tag? Keep it, or append `;Existing Vendor`.

The semicolon matters: a multi-select picklist is stored as semicolon-separated values, so assigning `Timing;Existing Vendor` back to the field is how you add a value without dropping one.

The rerun was 35 of 35, including the check that a second Existing Installer log does not duplicate the tag.

What is proven and what is inferred

The fix is proven by the acceptance run. The cause is not isolated. The most likely explanation is `CONTAINS` over a null text variable taking the whole expression to `null`, and the fact that moving `CONTAINS` behind `ISBLANK` fixed it is consistent with that — but `BLANKVALUE` should then have fixed it too, and it did not. Treat the nested-`IF` ordering as the working pattern, not as a documented rule of the formula engine.

## Why this one could have shipped

Everything a normal deploy checks passed. The metadata was valid, the flow activated, the interview ran without a fault, and every other field in the step updated correctly. A person clicking through the quick action once would have seen the Task land, the follow-up move, the status change — and would not have thought to open the multi-select and check a field that started out empty.

The list view hides part of the damage, which is worse rather than better. The **Prospects – Nurture** view has an `OR` that also matches `Last_Outreach_Outcome__c = Existing Installer`, so right after the call the builder does appear. But `Last_Outreach_Outcome__c` is current state: the next logged call, a text or a voicemail in two months, overwrites it. From that point the tag is the only thing keeping him on the list, and the tag was never written. The failure would have surfaced months later as a builder who quietly fell off the nurture list, which is about the least traceable shape a bug can take.

Test the field you wrote, not the flow you ran

“The flow ran without error” proves almost nothing about a formula-driven assignment. Read every target field back after the interview, including ones that start blank. The check that caught this one was a single `contains('Existing Vendor')` on a reloaded record.

## What the acceptance script pins down

The script is anonymous Apex, re-runnable, and builds its own fabricated records — invented builders such as _Brazos Bend Builders_ — deleting the previous run’s first. The cases that bear on this post:

scripts/apex/prospecting-board-acceptance.apex

```apex
logOutreach(c2.Id, today, 'Call', 'Existing Installer',
    'Happy with current installer. Open to a call after the new year.',
    null, 'Check in on installer relationship', today.addMonths(2));
Lead l2 = reload(c2.Id);
check('C2 tagged Nurturing_Reason__c Existing Vendor',
      l2.Nurturing_Reason__c != null && l2.Nurturing_Reason__c.contains('Existing Vendor'),
      l2.Nurturing_Reason__c);

// A second Existing Installer log must not duplicate the nurture reason.
logOutreach(c2.Id, today, 'Text', 'Existing Installer', null, null, null, null);
l2 = reload(c2.Id);
check('C2 nurture reason not duplicated',
      l2.Nurturing_Reason__c == 'Existing Vendor', l2.Nurturing_Reason__c);
```

The second log also passes blank inputs on purpose. A blank input on Log Outreach means “keep what the Lead already has” — finishing a call must never silently clear the next follow-up — and the same case asserts the two-month follow-up survived it. Same family of bug, opposite direction: a formula that turns _nothing entered_ into _something erased_.

## Practical summary

-   In a flow formula, a multi-select picklist field is only allowed inside a few functions. Copy it to a Text variable with an Assignment element before concatenating or using `CONTAINS`.
-   A formula that evaluates to `null` does not fault the interview. The assignment writes `null` and the flow reports success.
-   `Flow.Interview.getVariableValue` only returns variables marked Available for output. Debug a formula through a copy of the flow that assigns it to an output variable.
-   Test `ISBLANK` first in its own `IF` and only reach `CONTAINS` with a known non-blank value. In this build `BLANKVALUE` did not prevent the null; nesting did.
-   Append to a multi-select by assigning `existing & ";New Value"` back to the field, and check for the value first so repeated runs do not duplicate it.
-   Verify a formula-driven assignment by reading the target field back, especially when it starts empty — that is exactly the case where a silent `null` is invisible.

-   Flow
-   Flow formulas
-   Multi-select picklists
-   Lead
-   Testing
-   Anonymous Apex

Disclosure: Doorvana's owner is also a co-owner of Nocona Consulting. This is a real engagement between two separate companies, but not an arm's-length one — and not an independent reference. [More on the work page](https://noconaconsulting.com/work/).

## FAQ

### Why does a Salesforce flow formula fail with "Multi-select picklist fields are only supported in certain functions"?

Flow formulas only accept a multi-select picklist field inside a small set of functions such as INCLUDES, ISBLANK and ISNULL. Concatenating one with &, or passing it to CONTAINS, is rejected at deploy or save time. The usual workaround is to copy the field into a Text variable with an Assignment element first and build the formula on the variable, then assign the result back to the field as a semicolon-separated string.

### Does a Salesforce flow fault when a formula evaluates to null?

No. A formula resource that evaluates to null does not raise a fault or stop the interview. An Assignment element that uses it simply assigns null, and an Update Records element then saves that null. If the target field was already empty, nothing visibly changes, so the only way to catch it is to read the field back after the flow runs.

### Can Flow.Interview.getVariableValue read a flow formula or a private variable?

No. getVariableValue only returns variables marked Available for output; for anything else it returns null, which looks identical to a variable that genuinely holds null. To inspect an intermediate value from anonymous Apex, use a debug copy of the flow that assigns the formula's result to an output variable, then read that variable.

### How do you safely append a value to a multi-select picklist in a flow?

Copy the field to a Text variable, then branch with nested IF so ISBLANK is tested first: if the text is blank, assign the new value alone; only when it is known to be non-blank, use CONTAINS to check for the value and append ";Value" if it is missing. In this build, BLANKVALUE around the variable did not prevent the null result; ordering the tests so CONTAINS never sees a blank value did.
