A visible input where the user enters their phone number or email address; the component sends an OTP to that recipient, prompts the user to enter the code, and — only after successful verification — triggers a background data fetch and optionally populates downstream fields.
Read Form Rules before using this component — keys, labels, required fields, expressions, visibility, and validation all follow shared conventions.
Instead of customotpdatafetch, use… |
For… |
|---|---|
customphonenumber |
Plain phone number capture with no OTP verification |
customgenericdatafetch |
Background data lookups that do not require OTP verification |
customconditionaldatafetch |
Hidden, sequentially chained or guarded fetches without an OTP step |
customidinput |
National ID / Foreigner ID / Child ID / NIN verification via NIDA |
This is the OTP-verified variant of the data-fetch family: the user must prove they control the recipient (phone or email) before the data fetch can run.
| Prop | Type | Required? | Description |
|---|---|---|---|
label |
string |
required | Visible field label (Title Case, unique across form). |
required |
boolean |
required | Always set to false — controlled by the defaultRequired expression. |
defaultRequired |
boolean |
required | Set true to make this field required whenever it is visible. |
otpLength |
number |
optional | Number of digits in the OTP code; use 5 — matches all production endpoints. |
payloadKey |
string |
optional | Key name used when sending the recipient value to the OTP endpoint — "phoneNumber" for phone, "email" for email. |
useBaseUrl |
boolean |
optional | Prepend the API gateway base URL; set true for all production endpoints. |
placeholder |
string |
optional | Placeholder text shown inside the recipient input. |
hint |
string |
optional | Helper text below the input. |
clearRecipient |
boolean |
optional | When true, clears the recipient input after OTP verification completes; set false to preserve the value. |
allowOtpNumbersOnly |
boolean |
optional | When true, the OTP entry field accepts only numeric characters; set false to allow alphanumeric codes. |
isRecipientReadonly |
boolean |
optional | When true, the recipient input is pre-filled (via an expressions model binding) and the user cannot edit it; when false, the user types their own contact info. |
messageDataFetchComplete |
string |
optional | Success message displayed after the data fetch finishes; e.g. "Information retrieved successfully.". |
inputRegexValueValidation |
string |
optional | Regex applied to the recipient value before the OTP is sent — use ^07\\d{8}$ for Rwandan phone numbers or a standard email pattern. |
inputRegexValueValidationMessage |
string |
optional | Error message shown when inputRegexValueValidation fails; also set the same text under validation.messages.inputRegexValueValidation. |
disabled |
boolean |
optional | Renders the field as read-only. |
hidden |
boolean |
optional | Hides the field from the UI (prefer expressions.hide for conditional visibility). |
Every customotpdatafetch field must include this exact expression, regardless of whether the field is required or optional:
"expressions": {
"props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
}
All of the following keys must be set under validation.messages. The component emits them during its internal OTP and fetch lifecycle — none are added via validators.validation. A missing key leaves the user with a blank error state.
| Key | When emitted |
|---|---|
required |
Field is required but empty. |
otpExpired |
The OTP code has expired before the user submitted it. |
otpInvalid |
The user entered an incorrect OTP. |
otpRequired |
The user has not yet entered the OTP digits. |
dataFetchFailed |
The backend data fetch returned an error. |
dataFetchPending |
The data fetch is still in progress. |
requestOtpFailed |
The OTP send request failed. |
pendingRequestOtp |
The user has not yet requested an OTP. |
recipientNotFound |
No record found for the provided phone/email. |
dataFetchIncomplete |
OTP was verified but the data fetch did not complete. |
invalidRecipientEntry |
The recipient value is not a valid format. |
otpVerificationPending |
The user entered OTP digits but has not submitted them. |
endpointCodeNotConfigured |
Dev-time error: the backend endpoint code is missing. |
otpVerificationRequestFailed |
The OTP verification API call failed. |
inputRegexValueValidation |
Recipient value failed the inputRegexValueValidation pattern (use the same text as inputRegexValueValidationMessage). |
The user enters their phone number, receives an OTP, verifies it, and the backend data fetch fires automatically.
{
"key": "APPLICANT_PHONE_NUMBER",
"type": "customotpdatafetch",
"props": {
"label": "Phone Number",
"placeholder": "Enter your phone number",
"required": false,
"defaultRequired": true,
"otpLength": 5,
"payloadKey": "phoneNumber",
"useBaseUrl": true,
"clearRecipient": false,
"allowOtpNumbersOnly": false,
"isRecipientReadonly": false,
"messageDataFetchComplete": "Information retrieved successfully.",
"inputRegexValueValidation": "^07\\d{8}$",
"inputRegexValueValidationMessage": "Phone number should be 10 digits."
},
"expressions": {
"props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
},
"validation": {
"messages": {
"required": "This field is required.",
"otpExpired": "OTP expired.",
"otpInvalid": "Please enter a valid OTP.",
"otpRequired": "Please enter the OTP digits sent to the recipient.",
"dataFetchFailed": "Failed to complete data fetch. Please try again.",
"dataFetchPending": "Please wait while the information is retrieved.",
"requestOtpFailed": "Failed sending OTP request. Please try again.",
"pendingRequestOtp": "Request for an OTP to be sent to the recipient to proceed.",
"recipientNotFound": "Recipient not found.",
"dataFetchIncomplete": "OTP verification and data fetch incomplete.",
"invalidRecipientEntry": "Please enter a valid recipient.",
"otpVerificationPending": "Please submit the OTP for verification to proceed.",
"endpointCodeNotConfigured": "Endpoint code is required on this field.",
"otpVerificationRequestFailed": "OTP verification failed.",
"inputRegexValueValidation": "Phone number should be 10 digits."
}
}
}
The phone number is populated from another field in the model and the user cannot edit it; the field hides itself when the identification document type is not a Rwandan ID.
{
"key": "LANDOWNER_PHONE_NUMBER",
"type": "customotpdatafetch",
"props": {
"label": "Landowner Phone Number",
"hint": "The landowner phone number must be linked to their national ID.",
"placeholder": "Enter landowner phone number",
"required": false,
"defaultRequired": true,
"otpLength": 5,
"payloadKey": "phoneNumber",
"useBaseUrl": true,
"clearRecipient": false,
"allowOtpNumbersOnly": false,
"isRecipientReadonly": true,
"messageDataFetchComplete": "Information retrieved successfully.",
"inputRegexValueValidation": "^07\\d{8}$",
"inputRegexValueValidationMessage": "Phone number should be 10 digits starting with 07."
},
"expressions": {
"model.LANDOWNER_PHONE_NUMBER": "model?.LANDOWNER_DETAILS?.phoneNumber ?? null",
"hide": "!model?.IDENTIFICATION_DOCUMENT_TYPE || model?.IDENTIFICATION_DOCUMENT_TYPE !== 'RWANDAN_ID'",
"props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
},
"validation": {
"messages": {
"required": "This field is required.",
"otpExpired": "OTP expired.",
"otpInvalid": "Please enter a valid OTP.",
"otpRequired": "Please enter the OTP digits sent to the recipient.",
"dataFetchFailed": "Failed to complete data fetch. Please try again.",
"dataFetchPending": "Please wait while the information is retrieved.",
"requestOtpFailed": "Failed sending OTP request. Please try again.",
"pendingRequestOtp": "Request for an OTP to be sent to the recipient to proceed.",
"recipientNotFound": "Recipient not found.",
"dataFetchIncomplete": "OTP verification and data fetch incomplete.",
"invalidRecipientEntry": "Please enter a valid recipient.",
"otpVerificationPending": "Please submit the OTP for verification to proceed.",
"endpointCodeNotConfigured": "Endpoint code is required on this field.",
"otpVerificationRequestFailed": "OTP verification failed.",
"inputRegexValueValidation": "Phone number should be 10 digits starting with 07."
}
}
}
otpLength to 6 — all production endpoints issue 5-digit codes; mismatching this blocks the user from submitting a valid OTP.validation.messages keys — the component emits every one of them during normal usage; a missing key leaves the user with a blank error state.required: true directly — use required: false + defaultRequired: true + the expression; a hidden field with required: true blocks form submission.inputRegexValueValidation on a phone field — no format guard fires before the OTP send request, so invalid numbers are sent to the backend and generate a cryptic API error instead of an inline form message.inputRegexValueValidationMessage in props but omitting inputRegexValueValidation from validation.messages — the regex fires but no message text is shown.isRecipientReadonly: true without binding the value via expressions.model.<key> — the input is locked and empty; the user cannot proceed.payloadKey to a value the backend does not expect — for phone-based OTPs always use "phoneNumber", for email-based OTPs always use "email".hidden: true in props for conditional visibility — use expressions.hide with optional chaining instead; the hidden prop is not reactive.key is UPPER_SNAKE_CASE and unique across the entire formprops.label is present, unique, and correctly casedrequired: false is set (never true)defaultRequired is set (true or false)expressions["props.required"] is present with the exact required expressionotpLength is 5payloadKey is "phoneNumber" (phone) or "email" (email)useBaseUrl: trueinputRegexValueValidation is set for phone or email formatvalidation.messages keys are present (plus required)isRecipientReadonly: true, an expressions.model.<key> binding pre-fills the valueexpressions.hide, never the hidden prop