[readonly] markdown buffer
Screen Flow Component Validation Without Surprises
A custom LWC can look valid while Flow thinks it is invalid. It can also block Next without showing why, display an error too early, or lose an error supplied by Flow.
The validation rule is rarely the difficult part. The problem is misunderstanding the contract between Flow and the component.
That contract has three methods: validate(), setCustomValidity(), and reportValidity().
The useful rule is: calculate, store, then display.
Enter a reference, then choose Next.
- 01validate()calculate internal validitywaiting
- 02setCustomValidity()store Flow's external errorseparate
- 03reportValidity()display the right messagenot shown
Calculate internal validity
validate() should calculate the component's own validity and return Flow's expected shape.
get internalErrorMessage() {
if (!this.value) {
return 'Enter a reference number.';
}
if (!/^[A-Z]{3}-\d{6}$/.test(this.value)) {
return 'Use the format ABC-123456.';
}
return '';
}
@api
validate() {
const errorMessage = this.internalErrorMessage;
return {
isValid: errorMessage.length === 0,
errorMessage
};
}
Do not make validate() paint the UI. Keeping calculation separate makes the rules easy to test and leaves rendering to the reporting step.
Salesforce documents the lifecycle and call conditions, but the ownership split is the part worth remembering.
Store external errors separately
Flow can supply an error the component cannot calculate locally.
externalErrorMessage = '';
@api
setCustomValidity(externalErrorMessage) {
this.externalErrorMessage = externalErrorMessage ?? '';
}
Do not overwrite internal validation state. Flow may later clear its message with setCustomValidity(''), and that must not clear an invalid local format.
reportValidity() then decides what the user should see:
@api
reportValidity() {
const input = this.template.querySelector('lightning-input');
const message = this.externalErrorMessage || this.internalErrorMessage;
input.setCustomValidity(message);
input.reportValidity();
}
This version gives the external error priority. A complex component may render both in a component-level error region. Either way, blocked navigation needs a visible explanation.
Show errors at the right time
A required field is technically invalid on first load. That does not mean the screen should open as a wall of red.
Track whether the user has interacted or Flow has requested reporting. After a failed navigation attempt, keep validation live while the user corrects the value.
When an output property changes, dispatch FlowAttributeChangeEvent so Flow receives the current value. Validation and state synchronisation are related, but they are not the same contract.
import { FlowAttributeChangeEvent } from 'lightning/flowSupport';
handleChange(event) {
this.value = event.detail.value;
this.dispatchEvent(
new FlowAttributeChangeEvent('referenceNumber', this.value)
);
}
Keep the component's rules local. A postcode component should validate postcodes, not decide whether an order is eligible. Process-level rules belong in Flow, Apex, or a composite component that owns all relevant inputs.
Prove the whole contract
Test the public methods rather than only the private regex. Cover internal validity, external errors, clearing an external error while an internal error remains, delayed rendering, attribute-change events, and revisiting the screen with restored values.
A well-behaved component can answer three questions:
- Am I internally valid?
- Has Flow supplied an external error?
- What should the user see now?
Those questions map directly to the three methods.
Keep them separate, and Flow navigation stops feeling mysterious.