An API-driven repeater that creates one row per item returned from a fetch. The user fills in the visible fields per row but does not control row count.
Read Form Rules before using this component — keys, labels, required fields, expressions, visibility, and validation all follow shared conventions.
Extends Formly's FieldArrayType. Reads props.items at runtime (populated by a sibling data-fetch via populates), creates one row per item, and auto-injects hidden tracking fields (itemId, _isApiPopulated) into every row. Rows the user never modifies are excluded from the submitted form value.
customgenericdatafetch or customconditionaldatafetch responseInstead of customdynamicrepeater, use… |
For… |
|---|---|
customlabelvaluerepeater |
User-controlled row count (manual add/remove) |
customrepeater |
User-controlled rows that need uniqueness, minItems, maxItems |
customexpansionrepeater |
Long row templates that should collapse into expandable panels |
| Prop | Type | Required? | Description |
|---|---|---|---|
label |
string |
required | Heading for the repeater section. |
items |
array |
runtime | Populated by a sibling data-fetch via populates. Do not hardcode in JSON. |
dynamicRepeaterConfig.itemIdField |
string |
optional | Dot-path to the ID field in each item. Default "id". |
dynamicRepeaterConfig.itemLabelField |
string |
optional | Dot-path to the display label in each item. Default "label". |
dynamicRepeaterConfig.emptyStateMessage |
string |
optional | Message shown when props.items is empty. |
dynamicRepeaterConfig.enableDebugLogging |
boolean |
optional | Enables console debug output. Development only. |
hideField |
boolean |
optional | Hides the field and excludes its value from submission. Toggle via expressions["props.hideField"]. |
The component prepends these to every row automatically — do not declare them in fieldArray:
| Key | Description |
|---|---|
itemId |
Extracted ID value from props.items using itemIdField. |
_isApiPopulated |
true for rows that came from the API unchanged. |
Inside fieldArray sub-fields, use these template variables in props.label, props.placeholder, and props.hint:
| Variable | Resolves to |
|---|---|
{{itemLabel}} |
The value at dynamicRepeaterConfig.itemLabelField for that row's item |
{{itemId}} |
The value at dynamicRepeaterConfig.itemIdField for that row's item |
Template variables only work inside fieldArray fields, not on the repeater root props.
customdynamicrepeater
└── fieldArray
└── fieldGroup ← sub-fields live here
└── leaf fields
{
"key": "DOCUMENT_VERIFICATIONS",
"type": "customdynamicrepeater",
"props": {
"label": "Document Verifications",
"dynamicRepeaterConfig": {
"itemIdField": "documentId",
"itemLabelField": "documentName",
"emptyStateMessage": "No documents found for verification"
}
},
"fieldArray": {
"fieldGroup": [
{
"key": "VERIFICATION_STATUS",
"type": "customdropdown",
"props": {
"label": "Status for {{itemLabel}}",
"required": true,
"bindLabel": "label",
"bindValue": "value",
"options": [
{ "label": "Approved", "value": "APPROVED" },
{ "label": "Rejected", "value": "REJECTED" }
]
},
"validation": {
"messages": { "required": "Status is required." }
}
}
]
}
}
props.items is populated at runtime by a sibling customgenericdatafetch field via populates. The repeater only declares the row template:
{
"key": "SHAREHOLDER_LIST",
"type": "customdynamicrepeater",
"props": {
"label": "Shareholders",
"dynamicRepeaterConfig": {
"itemIdField": "shareholderId",
"itemLabelField": "shareholderName",
"emptyStateMessage": "No shareholders found for this registration number"
}
},
"fieldArray": {
"fieldGroup": [
{
"key": "SHARE_COUNT",
"type": "customcurrencyformatinput",
"props": {
"label": "Number of Shares — {{itemLabel}}",
"hint": "Shareholder ID: {{itemId}}",
"required": true
},
"validation": {
"messages": { "required": "Share count is required." }
}
},
{
"key": "SHARE_CLASS",
"type": "customdropdown",
"props": {
"label": "Share Class — {{itemLabel}}",
"required": true,
"bindLabel": "label",
"bindValue": "value",
"options": [
{ "label": "Ordinary", "value": "ORDINARY" },
{ "label": "Preference", "value": "PREFERENCE" }
]
},
"validation": {
"messages": { "required": "Share class is required." }
}
}
]
}
}
itemId or _isApiPopulated inside fieldArray.fieldGroup — the component injects them automatically; duplicates cause conflicts.props.items in the form JSON — items is populated at runtime by a data-fetch populates. Hardcoding bypasses the API-driven pattern.customlabelvaluerepeater when rows come from an API — switch to customdynamicrepeater so auto-injection and unmodified-row filtering work.customdynamicrepeater when the user controls the row count — there is no add/remove button. Use customlabelvaluerepeater instead.dynamicRepeaterConfig.itemIdField when the API items use a key other than "id" — the component silently stores undefined if the field doesn't exist.{{itemLabel}} or {{itemId}} outside fieldArray (e.g. on the repeater root label) — interpolation only runs inside row fields.key is UPPER_SNAKE_CASE and unique across the entire formprops.label is present, unique, and correctly casedsections > formly-group > block > customdynamicrepeaterprops.items via populates — do not hardcode itdynamicRepeaterConfig.itemIdField matches the actual ID key in the API responsedynamicRepeaterConfig.itemLabelField matches the actual label key in the API responsefieldArray is present and is an object (not an array)itemId or _isApiPopulated in fieldArray.fieldGroupdynamicRepeaterConfig.enableDebugLogging is false or removed before shipping