The form with no Next button
A screen flow that replaces the Lead New action had three screens with allowFinish set to false. The footer rendered, empty, and nobody could get past screen one. Why allowFinish governs the Next button too, the blank screen input that IsNull never matched, and the connector that looped back and erased everything the user had typed.
The form looked finished. Every field worked. The person filling it in picked a type, typed a name, an email and a phone number, and went to move on.
There was nothing to press. The footer was there — the divider line across the bottom of the modal, the padding, the space where the buttons go — and it was empty. No Next, no Previous, no Cancel inside the flow. The lead could not be created.
That was the client’s first evening entering real builder prospects into the sandbox. The screen had been built six weeks earlier, and nobody had ever clicked past it.
Why there was no other way in
At Doorvana the standard New Lead page cannot create a homeowner lead.
Lead.Company is required, the household name is supposed to be derived rather than typed, and
the platform refuses every layout workaround — the full story is in
the household model post. So lead creation
goes through a screen flow, New_Service_Lead, and the New action on Lead is overridden to
launch it.
An action override cannot point at a flow. ActionOverrideType has no Flow value, so the flow
sits inside a minimal Aura wrapper that implements lightning:actionOverride and hosts
lightning:flow:
force-app/main/default/aura/NewServiceLeadOverride/NewServiceLeadOverride.cmp
<aura:component implements="lightning:actionOverride,lightning:isUrlAddressable" access="global">
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<lightning:flow aura:id="serviceLeadFlow" onstatuschange="{!c.handleStatusChange}"/>
</aura:component> The override is set for both the Large and Small form factors:
force-app/main/default/objects/Lead/Lead.object-meta.xml
<actionOverrides>
<actionName>New</actionName>
<content>NewServiceLeadOverride</content>
<formFactor>Large</formFactor>
<skipRecordTypeSelect>false</skipRecordTypeSelect>
<type>LightningComponent</type>
</actionOverrides>
<actionOverrides>
<actionName>New</actionName>
<content>NewServiceLeadOverride</content>
<formFactor>Small</formFactor>
<skipRecordTypeSelect>false</skipRecordTypeSelect>
<type>LightningComponent</type>
</actionOverrides> That is the stakes. With New overridden on desktop and mobile, this flow is the only route by which a person can hand-key a Lead. A screen that cannot be left is not a cosmetic defect; it is the intake process stopping.
allowFinish is the Next button
Here is the first screen as it was built:
force-app/main/default/flows/New_Service_Lead.flow-meta.xml (before)
<screens>
<name>Who_Is_This</name>
<label>Who is this?</label>
<allowBack>true</allowBack>
<allowFinish>false</allowFinish>
<allowPause>false</allowPause>
<connector>
<targetReference>Business_Name_Supplied</targetReference>
</connector>
...
<showFooter>true</showFooter>
<showHeader>true</showHeader>
</screens> Read for navigation, that XML looks fine. There is a <connector> to the next element. The
footer is switched on. allowFinish false reads as “don’t let them finish the flow from here”,
which is exactly right for screen one of four.
It is not what it means. allowFinish controls the forward button, whatever it is labelled.
On a screen with an outgoing connector that button says Next; on the last screen it says
Finish. Set allowFinish to false and the button goes, connector or no connector. In Flow
Builder the same setting is the footer’s Next or Finish button, which is a much more honest
name than the metadata’s.
Then add the first-screen rule: there is nothing to go back to, so allowBack renders nothing
either. showFooter true with no buttons to show is a footer with nothing in it.
Three screens had it false: Who_Is_This, Need_Business_Name and What_Do_They_Need. The
fourth, Where_Is_It, had it true — which is why, in a flow nobody could get into, the one
screen nobody reached was the one that would have worked. Every other screen flow in the
project already set it correctly. The fix was three lines:
force-app/main/default/flows/New_Service_Lead.flow-meta.xml (diff)
<name>Who_Is_This</name>
<label>Who is this?</label>
<allowBack>true</allowBack>
- <allowFinish>false</allowFinish>
+ <allowFinish>true</allowFinish>
<allowPause>false</allowPause>
<connector>
<targetReference>Business_Name_Supplied</targetReference> Ruling out the obvious suspect
The org had recently moved to API 68, and “it worked before the release” is the first theory anyone reaches for. It was ruled out quickly, and the order of checks is worth stealing:
- The active flow version in the org matched source, so nothing had been edited in Setup behind the repo’s back.
- The Aura wrapper was unchanged since the day it was built.
- The flow’s own history showed
allowFinishfalse on those screens since that same day.
So it had never worked. It had simply never been clicked. The build-log entries for that first week record deploys and Apex tests; none of them record anyone walking through the screens.
Fixing the button exposed the next bug
With Next back, the flow could finally reach its own decision logic — and the first branch anyone tried did the wrong thing.
The rule: a lead for anyone other than a homeowner is named from its business, so if the type is not Residential and Business Name is blank, the flow should stop on a warning screen straight away, rather than let the user fill in two more screens and hit a validation rule on save. The decision was written like this:
force-app/main/default/flows/New_Service_Lead.flow-meta.xml (before)
<conditions>
<leftValueReference>Business_Name</leftValueReference>
<operator>IsNull</operator>
<rightValue>
<booleanValue>true</booleanValue>
</rightValue>
</conditions> In the browser, a Builder lead with a blank Business Name went straight past the warning to
What_Do_They_Need. The IsNull test did not match an input the user had left empty.
The working explanation is that a blank screen input reaches the decision as an empty string, not null. That is inferred from the behaviour and from what fixed it — it was not isolated.
The test steps written alongside the fix said to expect the warning screen. The first browser run contradicted them a few minutes later. That is a small thing and also the whole lesson: the expectation was written before anyone had seen the flow run, which is the same gap that let the missing button live for six weeks.
The fix that did not deploy
The first attempt compared the input to the empty-string global constant. The dry run refused it:
The "$GlobalConstant.EmptyString" element doesn't exist. Remove references to the element in all conditions.
What worked was taking the test out of the decision operator entirely and putting it in a Boolean formula resource:
force-app/main/default/flows/New_Service_Lead.flow-meta.xml
<formulas>
<description>A blank screen input does not satisfy IsNull in the decision, so blankness
is tested here. TRIM also catches a name that is only spaces.</description>
<name>Business_Name_Is_Blank</name>
<dataType>Boolean</dataType>
<expression>ISBLANK(TRIM({!Business_Name}))</expression>
</formulas> The decision now tests Business_Name_Is_Blank EqualTo true. Both cases — empty and
spaces-only — were confirmed in the browser.
One defect had been hiding the other by blocking the only route to it. That is worth expecting whenever a flow has been unreachable: fixing the entrance is the first test the rest of it has ever had.
A save that failed with nowhere to go
The Create_Lead element had no fault path. Any save error — a validation rule, a malformed
email — would end the interview on the platform’s generic flow error. It gained a
faultConnector to a Lead_Not_Saved screen that shows $Flow.FaultMessage and leaves
Previous available, so the user can correct entries instead of retyping them:
force-app/main/default/flows/New_Service_Lead.flow-meta.xml
<faultConnector>
<targetReference>Lead_Not_Saved</targetReference>
</faultConnector> A deliberately malformed email then reached that screen with no Lead created, which proved the
path. The raw message, though, was a paragraph of wrapper text around an
INVALID_EMAIL_ADDRESS code, ending in a pointer to a SOAP API guide — accurate and useless to
someone at a front desk.
Two formula resources now trim it. The first strips the sentence the platform puts before and
after the real error; the second drops a leading exception code such as
INVALID_EMAIL_ADDRESS:. Both fall back to the untouched message if the wording ever differs:
force-app/main/default/flows/New_Service_Lead.flow-meta.xml
<formulas>
<description>Removes the leading exception code, such as INVALID_EMAIL_ADDRESS, so the
user reads only the explanation.</description>
<name>Save_Error_Text</name>
<dataType>String</dataType>
<expression>IF(
REGEX({!Save_Error_Core}, "[A-Z_]+: .*"),
MID({!Save_Error_Core}, FIND(": ", {!Save_Error_Core}) + 2, LEN({!Save_Error_Core})),
{!Save_Error_Core}
)</expression>
</formulas> A 42-character First Name now reads
First Name: data value too large: ... (max length=40). — still the platform’s wording, but
without the paragraph around it.
The email case should not reach the save at all, so the Email screen field gained a
validationRule that stops a malformed address on screen one:
force-app/main/default/flows/New_Service_Lead.flow-meta.xml
<validationRule>
<errorMessage><p>Enter a valid email address, like name@example.com, or leave this blank.</p></errorMessage>
<formulaExpression>OR(ISBLANK({!Email}), REGEX({!Email}, "[^@\\s]+@[^@\\s]+\\.[^@\\s]{2,}"))</formulaExpression>
</validationRule> It checks the shape of the address and nothing more. A typo in a real-looking domain still passes, and nothing short of sending mail will catch that.
Going back is not the same as looping back
The Business Name warning screen originally said “Next returns you to the first screen”, and
it did: Need_Business_Name had a <connector> pointing back at Who_Is_This.
Following it forward produced the first screen, blank. Every name, email and phone number the user had typed was gone. Pressing Previous from the same warning screen produced the same first screen with every value intact. Previous restored entered values on every screen tested; the connector to the same screen did not.
The fix used the same setting that caused the first bug, this time on purpose. The connector
was removed and the warning screen set to allowFinish false, so Previous is the only button
on it:
force-app/main/default/flows/New_Service_Lead.flow-meta.xml (diff)
<allowBack>true</allowBack>
- <allowFinish>true</allowFinish>
+ <allowFinish>false</allowFinish>
<allowPause>false</allowPause>
- <connector>
- <targetReference>Who_Is_This</targetReference>
- </connector> The warning text changed to match: Click Previous to enter a Business Name — the lead cannot be saved without one. Your entries are kept.
What was verified, and what was not
Every fix was deployed to the sandbox and clicked through in the browser. A blank email, a
homeowner lead whose Company was derived as {First} {Last} Household by the before-save flow
Service_Lead_Stamp_Company, and the Builder path were each confirmed by saving fabricated
Leads, reading one back, and deleting them. Lead conversion was not part of these runs.
Practical summary
allowFinishon a screen controls the forward button — Next on a mid-flow screen, Finish on the last.allowFinishfalse with a valid<connector>still leaves no way forward.- On a flow’s first screen there is no Previous either, so
allowFinishfalse there renders an empty footer. - An
ActionOverrideTypecannot be Flow. Override New with an Aura component that implementslightning:actionOverrideand hostslightning:flow— and once you have, that flow is the only way in, so treat its navigation as critical path. IsNulltrue did not match a blank screen input. TestISBLANK(TRIM({!Input}))in a Boolean formula resource instead;$GlobalConstant.EmptyStringin a condition would not deploy.- Give every Create Records element in a screen flow a
faultConnector, and show a trimmed$Flow.FaultMessageon a screen that keeps Previous available. - Previous keeps entered values; a connector looping back to the same screen did not.
- A deploy, an activation and a green Apex run say nothing about whether a screen can be left. Click through every screen on every branch.