customdatasettable
A tabular display that renders rows already in the form model (STATIC) or fetched from a direct non-gateway URL (DYNAMIC); optionally supports row click selection.
A tabular display that renders rows already in the form model (dataType: "STATIC") or fetched directly from a non-gateway URL (dataType: "DYNAMIC"). Optionally used as a selection input when isSelectable: true — the user picks a row and the value is written to the form control.
Read Form Rules before using this component — keys, labels, required fields, expressions, visibility, and validation all follow shared conventions.
When to use
- Displaying rows already populated into the form model via
expressions(e.g. STATIC mode withprops.rowsbound tomodel?.SOMETHING || []) - Fetching tabular data from a direct backend URL that is not the integration gateway (
dataType: "DYNAMIC") - Using a table as a required form input where the user must click a row to select it (
isSelectable: true)
When NOT to use
Instead of customdatasettable, use… | For… |
|---|---|
customdatatable | Async fetches that route through the integration gateway (endpointCode + /integration/...) |
customitemselectiontable | Add/Remove row workflows where the user incrementally builds a list of selected items |
customeditabletable | Inline-editable grids with typed cell inputs |
customdropdowndatafetchpaginated | Single-value pick from a large paginated list when a dropdown UX is sufficient |
Props
| Prop | Type | Required? | Description |
|---|---|---|---|
columns | array | required | Column definitions — each entry: { "label": "Display Label", "key": "dot.notation.path", "sortable": false, "width": "auto", "nowrap": false } |
dataType | "STATIC" | "DYNAMIC" | optional | How data is sourced; default "STATIC". |
rows | array | required (STATIC) | Row data; always set via expressions at runtime, never hardcoded. |
dataset | object | required (DYNAMIC) | Direct URL fetch configuration (see below). |
useBaseUrl | boolean | optional | Prepend API gateway base URL to dataset.url. |
tableTitle | string | optional | Heading rendered above the table. |
required | boolean | optional | Always false. The required expression controls the runtime value. |
defaultRequired | boolean | optional | Used with the conditional required expression pattern (see below). |
isSelectable | boolean | optional | If true, rows can be clicked and the selection is written to the form control; default false. |
maxSelectable | number | optional | Maximum rows the user can select; default Infinity. |
selectionOutputKey | string | optional | Model key where the selected row value is written; defaults to field.key. |
selectionOutputPath | string | optional | Dot-notation path within the selected row to extract before writing to the model. |
isSortVisible | boolean | optional | Show sort controls. |
isSearchVisible | boolean | optional | Show search input. |
paginationVisible | boolean | optional | Show pagination controls. |
paginationInsideTable | boolean | optional | Render pagination inside the table element. |
pageSizes | array | optional | Available page size choices; default [10, 50, 100]. |
showDropdown | boolean | optional | Show a dropdown control in the table toolbar. |
roundedTable | boolean | optional | Apply rounded corners styling. |
searchPlaceholder | string | optional | Placeholder for the search input; default "Search". |
noDataMessage | string | optional | Message shown when no rows exist; default "No data available". |
tableClass | string | optional | CSS class applied to the <table> element; default "table". |
dataset object (when dataType: "DYNAMIC")
| Field | Type | Required? | Description |
|---|---|---|---|
url | string | required | Direct API URL; not routed through the integration gateway. |
dataField | string | optional | Dot-notation path to the rows array in the response. |
httpMethod | "POST" | "GET" | optional | HTTP method; defaults to GET. |
params | object | optional | Initial request params including page and size. |
headers | object | optional | Custom HTTP headers. |
searching | boolean | optional | If true, search box triggers a new API call. |
searchField | string | optional | Request param key for the search term. |
The required mechanism (when isSelectable: true)
When the table is used as a mandatory selection input, include the same conditional required expression used by other components:
"expressions": {
"props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
}The expression reads defaultRequired and automatically becomes false when the field is hidden.
Examples
Read-only display, rows from model
{
"key": "PARCELS",
"type": "customdatasettable",
"props": {
"label": "Parcels",
"dataType": "STATIC",
"rows": [],
"isSelectable": false,
"isSearchVisible": false,
"isSortVisible": false,
"paginationVisible": false,
"noDataMessage": "No parcels found",
"columns": [
{ "label": "Parcel ID", "key": "parcelId" },
{ "label": "Area (m²)", "key": "area" }
]
},
"expressions": {
"props.rows": "model?.FETCHED_PARCELS || []"
}
}Selectable table used as a required input
The user must pick a row; the conditional required expression ensures hidden rows don't block submission.
{
"key": "COMPANY_MANAGERS_TABLE",
"type": "customdatasettable",
"props": {
"label": "Select executive management to be beneficial owner",
"dataType": "STATIC",
"rows": [],
"required": false,
"defaultRequired": true,
"isSelectable": true,
"isSearchVisible": false,
"isSortVisible": false,
"paginationVisible": false,
"noDataMessage": "No managers found",
"columns": [
{ "label": "Document No", "key": "document_number" },
{ "label": "Name", "key": "name" },
{ "label": "Position", "key": "type" }
]
},
"expressions": {
"hide": "!(model?.SHOW_MANAGER_SELECTION === 'Yes')",
"props.rows": "model?.STUB_ALL_MANAGERS || []",
"props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
},
"validation": {
"messages": {
"required": "This field is required."
}
}
}Dynamic mode — direct non-gateway URL
Data is fetched directly from a backend URL with server-side pagination.
{
"key": "DISTRICT_STATS",
"type": "customdatasettable",
"props": {
"label": "District Statistics",
"dataType": "DYNAMIC",
"useBaseUrl": true,
"isSelectable": false,
"paginationVisible": true,
"noDataMessage": "No statistics available",
"dataset": {
"url": "/api/v1/districts/stats",
"dataField": "data.stats",
"httpMethod": "GET",
"params": {
"page": 0,
"size": 10
}
},
"columns": [
{ "label": "District", "key": "districtName" },
{ "label": "Total Applications", "key": "totalApplications" },
{ "label": "Approved", "key": "approved" },
{ "label": "Pending", "key": "pending" }
]
}
}Common mistakes
- Hardcoding
props.rowsas a static array — rows must always be set at runtime viaexpressions(e.g."props.rows": "model?.someKey || []"). - Using
customdatasettablewithdataType: "DYNAMIC"and an integration gateway endpoint — this component calls the URL directly without the gateway wrapper; usecustomdatatablewithdataset.endpointCodeinstead. - Confusing
columns[].key(dot-notation path into the row object) with a Formly field key —keyhere is a path into the data row, not a form field reference. - Using
isSelectable: truewithoutrequired/defaultRequiredand a validation message when row selection is mandatory — the table appears interactive but the form submits with no value. - Setting
isSelectable: truewithoutselectionOutputKeywhen the field key is not the intended model target — the selection is written to the field key by default. - Omitting
paginationVisible: truewhen usingdataType: "DYNAMIC"— pagination controls won't appear even though page/size params are sent.
Checklist
-
keyisUPPER_SNAKE_CASEand unique across the entire form -
props.labelis present, unique, and correctly cased -
columnsuse the{ "label": "...", "key": "..." }shape (note: opposite ofcustomdatatable) -
props.rowsis set viaexpressions, never hardcoded - When
isSelectable: true:required: false,defaultRequiredis set, andexpressions["props.required"]is present - When
isSelectable: trueand row data should be transformed:selectionOutputKeyand/orselectionOutputPathare set -
dataType: "DYNAMIC"uses a direct URL (not the integration gateway) -
validation.messages.requiredis present whenever the table is a required input - Not using
customdatasettablewhen the data must route through the integration gateway (usecustomdatatableinstead)
customcurrencyformatinput
A monetary amount input that displays comma-formatted values (e.g. 1,000,000 RWF) while preserving the raw formatted string in the model.
customdatatable
A read-only data table that displays rows fetched from an integration gateway endpoint or populated from the form model via expressions.