Custom properties in forms
How property definitions become form fields on the contact, company and deal resources, and how to reuse the field builder in your own resources.
Odden stores custom properties in each record's properties JSON column and describes them with Odden\Core\Models\PropertyDefinition records. The Filament plugin reads those definitions and adds matching fields to forms, so a new property shows up without code changes.
Where the fields appear
Fields are added to these forms:
| Resource | Entity type |
|---|---|
ContactResource |
contact |
CompanyResource |
company |
DealResource |
deal |
When at least one definition exists for the entity type, the form ends with a collapsible Custom Properties section. With no definitions, the section isn't shown.
The plugin only adds properties to forms. It doesn't add table columns, filters or infolist entries, even for definitions marked is_searchable.
Defining properties in the panel
Manage definitions under Settings › Custom Properties (PropertyDefinitionResource, /admin/property-definitions). The form fields map to PropertyDefinition attributes:
| Field | Attribute | Notes |
|---|---|---|
| Entity Type | entity_type |
The panel offers only contact and company. |
| Internal Key | name |
Must match ^[a-z0-9_]+$. This becomes the key in the properties column. |
| Display Label | label |
The field label. |
| Data Type | type |
An Odden\Core\Enums\PropertyType case. |
| Property Group | group_name |
Defaults to general. |
| Help Text / Description | description |
Shown as helper text under the field. |
| Dropdown Options | options |
Value to label pairs. Only shown for Select and Multi-Select. |
| Required field | is_required |
Makes the form field required. |
| Searchable in lists | is_searchable |
Stored, but the plugin doesn't use it. |
To add properties to deals, create definitions with entity_type set to deal in code, for example in a seeder or migration, because the panel's entity type select has no Deal option:
use Odden\Core\Enums\PropertyType;
use Odden\Core\Models\PropertyDefinition;
PropertyDefinition::create([
'entity_type' => 'deal',
'name' => 'procurement_contact',
'label' => 'Procurement Contact',
'type' => PropertyType::Text,
'group_name' => 'general',
]);Field types
Odden\Filament\Support\CustomPropertyFieldBuilder maps each PropertyType to a Filament field named properties.{name}:
PropertyType |
Filament field |
|---|---|
Text |
TextInput |
Number |
TextInput::numeric() |
Boolean |
Toggle |
Select |
Select with the definition's options |
MultiSelect |
Select::multiple() with the definition's options |
Date |
DatePicker |
DateTime |
DateTimePicker |
Json |
KeyValue |
Fields are ordered by the definition's sort_order. Each field uses the definition's label, is marked required when is_required is true, and shows description as helper text.
When the form is saved, the values are written to the record's properties column through Filament's dot-notation state. For example, a contact created with an annual_budget property of 50000 gets $contact->getProperty('annual_budget') === 50000.
The builder checks that the properties table exists before it queries definitions (the table name comes from the odden-core.tables.properties config key and defaults to odden_properties). If it doesn't exist, no section is added, so forms still render before you've run migrations.
Using the builder in your own resources
CustomPropertyFieldBuilder::makeSection(string $entityType): array returns either an empty array or an array holding a single Custom Properties section. Spread it into any form schema whose model uses the Odden\Core\Traits\HasCustomProperties trait:
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Schema;
use Odden\Filament\Support\CustomPropertyFieldBuilder;
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('email')->email()->required(),
...CustomPropertyFieldBuilder::makeSection('contact'),
]);
}The entity type you pass must match the entity_type of the definitions you want.
Property history
The contact, company and deal resources also show a read-only Property History / Audit Trail relation manager. It lists the record's propertyHistory rows: property, old value, new value, changed by (or "System"), source, and when the change happened.