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.
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 |
| 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. |
{
"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."
}
}
}
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."
}
}
}
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."
}
}
}
bindLabel or bindValue — dropdown items render blank; both are always required.bindValue: "value" on a dataset-driven dropdown where items are not { label, value } — use "" to store the whole object when the item shape varies.options nor dataset — the dropdown is always empty.dataset.dataField to a key that doesn't exist in the API response — the dropdown silently renders empty./integration/v1/fetch/sync — integration endpoints require dataset.httpMethod: "POST".customdropdown for province/district/sector hierarchies — use customcascadingdropdowns instead.key is UPPER_SNAKE_CASE and unique across the entire formprops.label is present, unique, and correctly casedsections > formly-group > block > customdropdownrequired: false is set (never true)defaultRequired is setexpressions["props.required"] is present with the exact required expressionbindLabel and bindValue are setoptions or dataset is providedsubType is set and dataset.httpMethod: "POST" is set for integration endpointsvalidation.messages.required is present