Components

customlabelvaluerepeater

A simple user-driven repeater where each row is a small label/value template. Use when the applicant manually adds and removes rows.

A simple user-driven repeater where each row is a small label/value template. Use when the applicant manually adds and removes rows.

Read Form Rules before using this component — keys, labels, required fields, expressions, visibility, and validation all follow shared conventions.

Extends Formly's FieldArrayType. Each row is an instance of the field template defined in fieldArray. The user adds rows with the add-row button and removes them per-row.

When to use

  • Beneficiaries with a name and relationship
  • Multiple phone numbers, alternative email addresses
  • Lists of references, witnesses, or contacts
  • Small variable-length structured data the user types in directly

When NOT to use

Instead of customlabelvaluerepeater, use…For…
customrepeaterLarger row templates that need minItems / maxItems, uniqueness validators, or hideLabel
customexpansionrepeaterLong row templates that should collapse into expandable panels
customdynamicrepeaterRows that come from an API — the user does not control the row count

Props

PropTypeRequired?Description
labelstringrequiredRendered as the fieldset <legend> — the heading for the repeater.
descriptionstringoptionalRendered as a <p> below the legend.
requiredbooleanoptionalMarks the repeater as required (asterisk on label).
addTextstringoptionalLabel on the add-row button; defaults to a generic add label if omitted.
hideFieldbooleanoptionalHides the field and excludes its value from submission. Toggle via expressions["props.hideField"].

Sub-field placement

Fields live inside fieldArray.fieldGroup, not inside fieldGroup at the repeater level. fieldArray is a single field-config object, not an array.

customlabelvaluerepeater
  └── fieldArray
        └── fieldGroup   ← sub-fields live here
              └── leaf fields

Examples

Minimal — beneficiaries

{
  "key": "BENEFICIARIES",
  "type": "customlabelvaluerepeater",
  "props": {
    "label": "Beneficiaries",
    "addText": "Add Beneficiary"
  },
  "fieldArray": {
    "fieldGroup": [
      {
        "key": "FULL_NAME",
        "type": "input",
        "props": {
          "label": "Full Name",
          "required": true
        },
        "validation": {
          "messages": { "required": "Full name is required." }
        }
      },
      {
        "key": "RELATIONSHIP",
        "type": "customdropdown",
        "props": {
          "label": "Relationship",
          "required": true,
          "bindLabel": "label",
          "bindValue": "value",
          "options": [
            { "label": "Spouse", "value": "SPOUSE" },
            { "label": "Child", "value": "CHILD" },
            { "label": "Parent", "value": "PARENT" }
          ]
        },
        "validation": {
          "messages": { "required": "Relationship is required." }
        }
      }
    ]
  }
}

Full example — shareholders with NID lookup

{
  "key": "SHAREHOLDERS",
  "type": "customlabelvaluerepeater",
  "props": {
    "label": "Shareholders",
    "description": "Add all shareholders with their details.",
    "required": true,
    "addText": "Add Shareholder"
  },
  "validation": {
    "messages": {
      "required": "At least one shareholder is required."
    }
  },
  "fieldArray": {
    "fieldGroup": [
      {
        "key": "SHAREHOLDER_NID",
        "type": "customidinput",
        "props": {
          "label": "Shareholder National ID",
          "required": true,
          "idType": "NID",
          "nidType": "NATIONAL_ID",
          "endpointCode": "NIDAGETIDINFO",
          "populates": [
            { "valueKey": "forename", "targetKey": "SHAREHOLDER_FIRST_NAME" },
            { "valueKey": "surname", "targetKey": "SHAREHOLDER_LAST_NAME" },
            { "valueKey": "dateOfBirth", "targetKey": "SHAREHOLDER_DOB" }
          ]
        },
        "validation": {
          "messages": {
            "required": "Shareholder National ID is required.",
            "invalidNidInput": "Enter a valid 16-digit Rwandan National ID."
          }
        }
      },
      {
        "key": "SHAREHOLDER_FIRST_NAME",
        "type": "input",
        "props": {
          "label": "First Name",
          "required": true,
          "readonly": true
        },
        "validation": {
          "messages": { "required": "First name is required." }
        }
      },
      {
        "key": "SHAREHOLDER_LAST_NAME",
        "type": "input",
        "props": {
          "label": "Last Name",
          "required": true,
          "readonly": true
        },
        "validation": {
          "messages": { "required": "Last name is required." }
        }
      },
      {
        "key": "SHARE_PERCENTAGE",
        "type": "customcurrencyformatinput",
        "props": {
          "label": "Share Percentage (%)",
          "required": true
        },
        "validation": {
          "messages": { "required": "Share percentage is required." }
        }
      }
    ]
  }
}

Common mistakes

  • Placing fields directly inside fieldGroup on the repeater root instead of inside fieldArray.fieldGroup — rows won't render.
  • Omitting fieldArray entirely — the repeater renders an empty shell with no row template.
  • Using fieldArray as an array ([]) instead of an object ({}) — fieldArray is a single field config object, not a list.
  • Using customlabelvaluerepeater when rows come from an API — use customdynamicrepeater so auto-injection and unmodified-row filtering work correctly.
  • Reaching for customlabelvaluerepeater when uniqueness, minItems/maxItems, or conditional disabling is needed — use customrepeater instead.

Checklist

  • key is UPPER_SNAKE_CASE and unique across the entire form
  • props.label is present, unique, and correctly cased
  • Field is nested at the correct depth: sections > formly-group > block > customlabelvaluerepeater
  • fieldArray is present and is an object (not an array)
  • Sub-fields live inside fieldArray.fieldGroup, not fieldGroup
  • If the repeater is required, props.required: true is set and validation.messages.required is provided
  • No nested repeaters

On this page