Components

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…
customphonenumberPlain phone number capture with no OTP verification
customgenericdatafetchBackground data lookups that do not require OTP verification
customconditionaldatafetchHidden, sequentially chained or guarded fetches without an OTP step
customidinputNational 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

PropTypeRequired?Description
labelstringrequiredVisible field label (Title Case, unique across form).
requiredbooleanrequiredAlways set to false — controlled by the defaultRequired expression.
defaultRequiredbooleanrequiredSet true to make this field required whenever it is visible.
otpLengthnumberoptionalNumber of digits in the OTP code; use 5 — matches all production endpoints.
payloadKeystringoptionalKey name used when sending the recipient value to the OTP endpoint — "phoneNumber" for phone, "email" for email.
useBaseUrlbooleanoptionalPrepend the API gateway base URL; set true for all production endpoints.
placeholderstringoptionalPlaceholder text shown inside the recipient input.
hintstringoptionalHelper text below the input.
clearRecipientbooleanoptionalWhen true, clears the recipient input after OTP verification completes; set false to preserve the value.
allowOtpNumbersOnlybooleanoptionalWhen true, the OTP entry field accepts only numeric characters; set false to allow alphanumeric codes.
isRecipientReadonlybooleanoptionalWhen 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.
messageDataFetchCompletestringoptionalSuccess message displayed after the data fetch finishes; e.g. "Information retrieved successfully.".
inputRegexValueValidationstringoptionalRegex applied to the recipient value before the OTP is sent — use ^07\\d{8}$ for Rwandan phone numbers or a standard email pattern.
inputRegexValueValidationMessagestringoptionalError message shown when inputRegexValueValidation fails; also set the same text under validation.messages.inputRegexValueValidation.
disabledbooleanoptionalRenders the field as read-only.
hiddenbooleanoptionalHides 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.

KeyWhen emitted
requiredField is required but empty.
otpExpiredThe OTP code has expired before the user submitted it.
otpInvalidThe user entered an incorrect OTP.
otpRequiredThe user has not yet entered the OTP digits.
dataFetchFailedThe backend data fetch returned an error.
dataFetchPendingThe data fetch is still in progress.
requestOtpFailedThe OTP send request failed.
pendingRequestOtpThe user has not yet requested an OTP.
recipientNotFoundNo record found for the provided phone/email.
dataFetchIncompleteOTP was verified but the data fetch did not complete.
invalidRecipientEntryThe recipient value is not a valid format.
otpVerificationPendingThe user entered OTP digits but has not submitted them.
endpointCodeNotConfiguredDev-time error: the backend endpoint code is missing.
otpVerificationRequestFailedThe OTP verification API call failed.
inputRegexValueValidationRecipient 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 otpLength to 6 — all production endpoints issue 5-digit codes; mismatching this blocks the user from submitting a valid OTP.
  • Omitting any of the 14 validation.messages keys — the component emits every one of them during normal usage; a missing key leaves the user with a blank error state.
  • Setting required: true directly — use required: false + defaultRequired: true + the expression; a hidden field with required: true blocks form submission.
  • Omitting 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.
  • Setting inputRegexValueValidationMessage in props but omitting inputRegexValueValidation from validation.messages — the regex fires but no message text is shown.
  • Setting isRecipientReadonly: true without binding the value via expressions.model.<key> — the input is locked and empty; the user cannot proceed.
  • Setting payloadKey to a value the backend does not expect — for phone-based OTPs always use "phoneNumber", for email-based OTPs always use "email".
  • Using hidden: true in props for conditional visibility — use expressions.hide with optional chaining instead; the hidden prop is not reactive.

Checklist

  • key is UPPER_SNAKE_CASE and unique across the entire form
  • props.label is present, unique, and correctly cased
  • required: false is set (never true)
  • defaultRequired is set (true or false)
  • expressions["props.required"] is present with the exact required expression
  • otpLength is 5
  • payloadKey is "phoneNumber" (phone) or "email" (email)
  • useBaseUrl: true
  • inputRegexValueValidation is set for phone or email format
  • All 14 validation.messages keys are present (plus required)
  • When isRecipientReadonly: true, an expressions.model.<key> binding pre-fills the value
  • Conditional visibility uses expressions.hide, never the hidden prop

On this page