Skip to Content
FrontendBundlesCRUDCustom Properties

Custom Properties

Custom properties allow any entity to have extra user-defined fields (text, number, boolean, date, dropdown, entity references, etc.) without frontend code changes. The system has two components:

  • CustomPropertiesFormSection — renders the dynamic fields on create/edit forms
  • AdditionalInformationSection — renders the values on the detail/show page

How It Works

Backend: custom_properties table (tableName, propertyName, fieldType, source, priority, enabled) ├──── customPropertiesListing URL embedded in entity API response Form page (create/edit) Show page (detail) CustomPropertiesFormSection AdditionalInformationSection ├── fetch definitions from ├── reads data.customPropertiesListing │ customPropertiesListingUrl ├── fetches definitions via getItemAction ├── sort by priority ascending ├── sort by priority ascending ├── map fieldType → ColumnTypeEnum ├── map fieldType → ColumnTypeEnum └── render via RenderFormFields └── render via GeneralDetails └── writes to form: └── reads from: customProperties.{name} data.customProperties.{name}

On form submit, the customProperties object is sent as customData in the API payload.


Form Component: CustomPropertiesFormSection

Import

import CustomPropertiesFormSection from "@phpcreation/frontend-crud-react-nextjs-bundle/components/CustomPropertiesFormSection";

Props

PropTypeRequiredDefaultDescription
customPropertiesListingUrlstringFull API URL returning property definitions for this entity
dataRecord<string, any>Existing entity data; pre-fills customProperties.* on edit
fqcn_buiIFQCN_BUIBundle/Unit/Interface identifier for ID generation
localestringLocale string for useAppTranslations
tenantstringTenant identifier passed to API calls
isSubmittingbooleanfalseDisables all inputs when the parent form is submitting
sectionTitlestring"Additional Information"Section heading shown above the fields

Usage Examples

"use client"; import { useForm, FormProvider } from "react-hook-form"; import CustomPropertiesFormSection from "@phpcreation/frontend-crud-react-nextjs-bundle/components/CustomPropertiesFormSection"; export default function ProductCreateForm({ tenant, locale }) { const form = useForm(); return ( <FormProvider {...form}> <form onSubmit={form.handleSubmit(onSubmit)}> {/* ... standard form fields ... */} <CustomPropertiesFormSection customPropertiesListingUrl={ `/open-api/v3/custom_properties?tableName=products&enabled=true` } fqcn_bui={{ Bundle: "phprCrud", Unit: "Product", Interface: "create" }} locale={locale} tenant={tenant} /> </form> </FormProvider> ); }

Form Behavior

  • De-duplicated fetches: uses a useRef to call the API only once per unique URL, even across re-renders.
  • Priority ordering: definitions are sorted by priority ascending before rendering.
  • Invisible until ready: returns null while loading or if no definitions are found.
  • Pre-fill on edit: a second useEffect populates form values from data.customProperties after definitions load.

Show Page Component: AdditionalInformationSection

Used inside detail/show pages. It reads the customPropertiesListing URL from the entity API response, fetches the property definitions, and displays values via GeneralDetails.

How the Show Page Gets the URL

The API response for any entity that has custom properties includes a customPropertiesListing field — a URL pointing to the filtered custom property definitions:

{ "id": 42, "name": "Blue Widget", "customProperties": { "color": "blue", "size": "M" }, "customPropertiesListing": "/open-api/v3/custom_properties?tableName=products&enabled=true" }

AdditionalInformationSection reads data.customPropertiesListing from DetailShowContext, fetches the definitions, and maps each one to a detailsInfo entry with key: "customProperties.{propertyName}".

Props

PropTypeRequiredDefaultDescription
fqcn_buiIFQCN_BUIBundle/Unit/Interface identifier
localestringLocale string
tenantstringTenant identifier
userUser | nullCurrent user (passed through to GeneralDetails)
handleRemoveSection(section: string) => voidSection removal callback
handleRemoveField(section: string, field: string) => voidField removal callback
sectionTitlestring"Additional Information"Section heading
onVisibilityChange(hasData: boolean) => voidCalled with true/false based on whether definitions were found
formInputsFormInput[]Additional form inputs passed to GeneralDetails
loadingKVSbooleanKVS loading state

Usage

AdditionalInformationSection is used inside your show page container, alongside other GeneralDetails sections:

import AdditionalInformationSection from "@phpcreation/frontend-crud-react-nextjs-bundle/components/DetailShow/AdditionalInformationSection"; // In your show page: <AdditionalInformationSection fqcn_bui={fqcn_bui} locale={locale} tenant={tenant} user={user} handleRemoveSection={handleRemoveSection} handleRemoveField={handleRemoveField} onVisibilityChange={(hasData) => setShowCustomSection(hasData)} />

The component returns null automatically if the entity has no custom properties or none are enabled.


Field Type Mapping

Both components use the same API_TYPE_TO_COLUMN_TYPE mapping exported from CustomPropertiesFormSection:

API fieldType ValueColumnTypeEnum
text, stringTEXT
moneyCURRENCY
htmlTEXTAREA
int, integerNUMBER
float, doubleFLOAT
bool, booleanBOOLEAN
dateDATE
datetimeDATETIME
entitySELECT_ASYNC (uses source as targetResourceAsync)
dropdownSELECT

Data Structure

Form Values (during edit)

{ name: "Blue Widget", price: 19.99, customProperties: { color: "blue", size: "M", weight: 0.5 } }

API Payload (on submit)

On submit, customProperties is sent as customData:

{ name: "Blue Widget", price: 19.99, customData: { color: "blue", size: "M", weight: 0.5 } }

API Response (on fetch — for show page)

{ "id": 42, "name": "Blue Widget", "customProperties": { "color": "blue", "size": "M" }, "customPropertiesListing": "/open-api/v3/custom_properties?tableName=products&enabled=true" }

The customPropertiesListing URL in the API response is what AdditionalInformationSection uses to fetch property definitions on the show page. The customPropertiesListingUrl prop on CustomPropertiesFormSection serves the same purpose on form pages — you must set them to the same filtered URL.


Managing Custom Properties

Custom properties are managed through the StorageBundle/CustomProperty CRUD entity. Each property definition specifies:

  • tableName — which entity table the property belongs to
  • propertyName — the key used to store/retrieve the value
  • fieldType — the input/display type
  • priority — display order (ascending)
  • enabled — whether the property is active

See:

Always include a tableName filter and enabled=true in the listing URL to return only the active properties for the target entity, e.g. ?tableName=products&enabled=true.

Last updated on