Components

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 with props.rows bound to model?.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…
customdatatableAsync fetches that route through the integration gateway (endpointCode + /integration/...)
customitemselectiontableAdd/Remove row workflows where the user incrementally builds a list of selected items
customeditabletableInline-editable grids with typed cell inputs
customdropdowndatafetchpaginatedSingle-value pick from a large paginated list when a dropdown UX is sufficient

Props

PropTypeRequired?Description
columnsarrayrequiredColumn definitions — each entry: { "label": "Display Label", "key": "dot.notation.path", "sortable": false, "width": "auto", "nowrap": false }
dataType"STATIC" | "DYNAMIC"optionalHow data is sourced; default "STATIC".
rowsarrayrequired (STATIC)Row data; always set via expressions at runtime, never hardcoded.
datasetobjectrequired (DYNAMIC)Direct URL fetch configuration (see below).
useBaseUrlbooleanoptionalPrepend API gateway base URL to dataset.url.
tableTitlestringoptionalHeading rendered above the table.
requiredbooleanoptionalAlways false. The required expression controls the runtime value.
defaultRequiredbooleanoptionalUsed with the conditional required expression pattern (see below).
isSelectablebooleanoptionalIf true, rows can be clicked and the selection is written to the form control; default false.
maxSelectablenumberoptionalMaximum rows the user can select; default Infinity.
selectionOutputKeystringoptionalModel key where the selected row value is written; defaults to field.key.
selectionOutputPathstringoptionalDot-notation path within the selected row to extract before writing to the model.
isSortVisiblebooleanoptionalShow sort controls.
isSearchVisiblebooleanoptionalShow search input.
paginationVisiblebooleanoptionalShow pagination controls.
paginationInsideTablebooleanoptionalRender pagination inside the table element.
pageSizesarrayoptionalAvailable page size choices; default [10, 50, 100].
showDropdownbooleanoptionalShow a dropdown control in the table toolbar.
roundedTablebooleanoptionalApply rounded corners styling.
searchPlaceholderstringoptionalPlaceholder for the search input; default "Search".
noDataMessagestringoptionalMessage shown when no rows exist; default "No data available".
tableClassstringoptionalCSS class applied to the <table> element; default "table".

dataset object (when dataType: "DYNAMIC")

FieldTypeRequired?Description
urlstringrequiredDirect API URL; not routed through the integration gateway.
dataFieldstringoptionalDot-notation path to the rows array in the response.
httpMethod"POST" | "GET"optionalHTTP method; defaults to GET.
paramsobjectoptionalInitial request params including page and size.
headersobjectoptionalCustom HTTP headers.
searchingbooleanoptionalIf true, search box triggers a new API call.
searchFieldstringoptionalRequest 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.rows as a static array — rows must always be set at runtime via expressions (e.g. "props.rows": "model?.someKey || []").
  • Using customdatasettable with dataType: "DYNAMIC" and an integration gateway endpoint — this component calls the URL directly without the gateway wrapper; use customdatatable with dataset.endpointCode instead.
  • Confusing columns[].key (dot-notation path into the row object) with a Formly field key — key here is a path into the data row, not a form field reference.
  • Using isSelectable: true without required/defaultRequired and a validation message when row selection is mandatory — the table appears interactive but the form submits with no value.
  • Setting isSelectable: true without selectionOutputKey when the field key is not the intended model target — the selection is written to the field key by default.
  • Omitting paginationVisible: true when using dataType: "DYNAMIC" — pagination controls won't appear even though page/size params are sent.

Checklist

  • key is UPPER_SNAKE_CASE and unique across the entire form
  • props.label is present, unique, and correctly cased
  • columns use the { "label": "...", "key": "..." } shape (note: opposite of customdatatable)
  • props.rows is set via expressions, never hardcoded
  • When isSelectable: true: required: false, defaultRequired is set, and expressions["props.required"] is present
  • When isSelectable: true and row data should be transformed: selectionOutputKey and/or selectionOutputPath are set
  • dataType: "DYNAMIC" uses a direct URL (not the integration gateway)
  • validation.messages.required is present whenever the table is a required input
  • Not using customdatasettable when the data must route through the integration gateway (use customdatatable instead)

On this page