Components
customdropdown
A single-select or multi-select dropdown. Supports static option lists, admin dataset endpoints, and integration endpoints.
A single-select or multi-select dropdown. Supports static option lists, admin dataset endpoints, and integration endpoints.
Read Form Rules before using this component — keys, labels, required fields, expressions, visibility, and validation all follow shared conventions.
When to use
- Selecting from a known, bounded list of values (citizenship, status, category, nationality)
- Static options or API-driven options from an admin dataset or integration endpoint
- Single or multiple selection
When NOT to use
Instead of customdropdown, use… | For… |
|---|---|
customdropdownpaginated | Large option lists that need server-side pagination with scroll-to-load |
customdropdowndatafetchpaginated | API-driven options with pagination |
customcascadingdropdowns | Province → District → Sector → Cell → Village and other hierarchical selection |
radio | 2–4 mutually exclusive options that benefit from always being visible |
select | Never — select is the plain Formly fallback and is not used in production forms |
Props
| Prop | Type | Required? | Description |
|---|---|---|---|
label | string | required | Visible label. Title Case for nouns, Sentence case for questions. Must be unique across the form. |
required | boolean | required | Always false. The expression controls the runtime value. |
defaultRequired | boolean | required | true for required fields, false for optional ones. |
bindLabel | string | required | Key in each option object to display (e.g. "label", "name"). |
bindValue | string | required | Key in each option object to store. Use "" to store the whole object. |
options | array | required if no dataset | Static list of option objects. Typically { label, value }. |
dataset | object | required if no options | API data source. Use for options loaded at runtime. |
subType | string | optional | "CUSTOM_DROPDOWN_DATASET" for admin dataset endpoints; "CUSTOM_DROPDOWN_DATAFETCH" for integration endpoints. |
multiple | boolean | optional | true to allow multiple selections. |
placeholder | string | optional | Ghost text shown before a selection is made. |
virtualScroll | boolean | optional | Enable virtual scrolling for large option lists. |
alwaysEnabled | boolean | optional | Prevents the field being disabled by computed expressions. |
tooltip | string | optional | Help text shown as an icon tooltip next to the label. |
hint | string | optional | Help text rendered below the field, always visible. |
hideField | boolean | optional | Hides the field and excludes its value. Toggle via expressions["props.hideField"]. |
dataset object
| Field | Type | Description |
|---|---|---|
url | string | API endpoint path. |
dataField | string | Key in the response body that contains the items array (e.g. "data", "data.content"). |
useBaseUrl | boolean | Always true for production endpoints. |
httpMethod | string | Default GET. Set "POST" for integration endpoints (/integration/v1/fetch/sync). |
params | object | Query or body parameters sent to the API. |
Examples
Static options
{
"key": "MARITAL_STATUS",
"type": "customdropdown",
"props": {
"label": "Marital Status",
"placeholder": "Select marital status",
"required": false,
"defaultRequired": true,
"bindLabel": "label",
"bindValue": "value",
"options": [
{ "label": "Single", "value": "SINGLE" },
{ "label": "Married", "value": "MARRIED" },
{ "label": "Divorced", "value": "DIVORCED" },
{ "label": "Widowed", "value": "WIDOWED" }
]
},
"expressions": {
"props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
},
"validation": {
"messages": {
"required": "This field is required."
}
}
}Admin dataset endpoint
Use subType: "CUSTOM_DROPDOWN_DATASET" and dataset.url pointing to /admin/v1/dataset-items/by-dataset-code/<CODE>. Set bindValue: "" to store the whole object when items are not a simple { label, value } pair.
{
"key": "COUNTRY_OF_NATIONALITY",
"type": "customdropdown",
"props": {
"label": "Country of Nationality",
"placeholder": "Select country",
"required": false,
"defaultRequired": true,
"bindLabel": "label",
"bindValue": "",
"subType": "CUSTOM_DROPDOWN_DATASET",
"useBaseUrl": true,
"dataset": {
"url": "/admin/v1/dataset-items/by-dataset-code/COUNTRY",
"dataField": "data"
}
},
"expressions": {
"props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
},
"validation": {
"messages": {
"required": "This field is required."
}
}
}Integration endpoint
Use subType: "CUSTOM_DROPDOWN_DATAFETCH", dataset.httpMethod: "POST", and dataset.url: "/integration/v1/fetch/sync".
{
"key": "COUNTRY_OF_STUDY",
"type": "customdropdown",
"props": {
"label": "Country of Study",
"placeholder": "Select country",
"required": false,
"defaultRequired": true,
"bindLabel": "name",
"bindValue": "",
"subType": "CUSTOM_DROPDOWN_DATAFETCH",
"useBaseUrl": true,
"virtualScroll": true,
"dataset": {
"url": "/integration/v1/fetch/sync",
"dataField": "data",
"httpMethod": "POST"
}
},
"expressions": {
"props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
},
"validation": {
"messages": {
"required": "This field is required."
}
}
}Common mistakes
- Omitting
bindLabelorbindValue— dropdown items render blank; both are always required. - Setting
bindValue: "value"on a dataset-driven dropdown where items are not{ label, value }— use""to store the whole object when the item shape varies. - Providing neither
optionsnordataset— the dropdown is always empty. - Setting
dataset.dataFieldto a key that doesn't exist in the API response — the dropdown silently renders empty. - Using a GET request against
/integration/v1/fetch/sync— integration endpoints requiredataset.httpMethod: "POST". - Using
customdropdownfor province/district/sector hierarchies — usecustomcascadingdropdownsinstead.
Checklist
-
keyisUPPER_SNAKE_CASEand unique across the entire form -
props.labelis present, unique, and correctly cased - Field is nested at the correct depth:
sections > formly-group > block > customdropdown -
required: falseis set (nevertrue) -
defaultRequiredis set -
expressions["props.required"]is present with the exact required expression -
bindLabelandbindValueare set - Exactly one of
optionsordatasetis provided - For dataset-driven:
subTypeis set anddataset.httpMethod: "POST"is set for integration endpoints -
validation.messages.requiredis present