customotpdatafetch
A visible recipient input that sends an OTP to a phone or email, prompts for verification, and then triggers a background data fetch on success.
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.
When to use
- Verify ownership of a phone number or email before retrieving sensitive data (e.g. landowner profile, applicant record)
- Pre-filled, read-only recipient that needs OTP verification before downstream fetches run
- Any flow that combines "send OTP → user enters code → fetch profile" in a single field
When NOT to use
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.
Props
| 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). |
The required mechanism
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"
}Validation messages
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). |
Examples
Editable phone number with OTP
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."
}
}
}Pre-filled, read-only recipient
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."
}
}
}Common mistakes
- Setting
otpLengthto6— all production endpoints issue 5-digit codes; mismatching this blocks the user from submitting a valid OTP. - Omitting any of the 14
validation.messageskeys — the component emits every one of them during normal usage; a missing key leaves the user with a blank error state. - Setting
required: truedirectly — userequired: false+defaultRequired: true+ the expression; a hidden field withrequired: trueblocks form submission. - Omitting
inputRegexValueValidationon 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. - Setting
inputRegexValueValidationMessagein props but omittinginputRegexValueValidationfromvalidation.messages— the regex fires but no message text is shown. - Setting
isRecipientReadonly: truewithout binding the value viaexpressions.model.<key>— the input is locked and empty; the user cannot proceed. - Setting
payloadKeyto a value the backend does not expect — for phone-based OTPs always use"phoneNumber", for email-based OTPs always use"email". - Using
hidden: truein props for conditional visibility — useexpressions.hidewith optional chaining instead; thehiddenprop is not reactive.
Checklist
-
keyisUPPER_SNAKE_CASEand unique across the entire form -
props.labelis present, unique, and correctly cased -
required: falseis set (nevertrue) -
defaultRequiredis set (trueorfalse) -
expressions["props.required"]is present with the exact required expression -
otpLengthis5 -
payloadKeyis"phoneNumber"(phone) or"email"(email) -
useBaseUrl: true -
inputRegexValueValidationis set for phone or email format - All 14
validation.messageskeys are present (plusrequired) - When
isRecipientReadonly: true, anexpressions.model.<key>binding pre-fills the value - Conditional visibility uses
expressions.hide, never thehiddenprop
custommultiselectdatafetch
A search-filter form plus results table with checkbox selection, driven by an integration engine endpoint. Use when the user must search a dynamic dataset and pick one or more results.
customphonenumber
A phone number input backed by intl-tel-input with a country-code flag dropdown and format validation.