Global EAV
Overview
- Scope
- Global custom-field subsystem under app/Services/Eav and its database tables
Key concepts
- Custom Field
- Definition of a typed, active/required/system field for an entity_type.
- Typed Value
- A value stored in exactly the column selected by its FieldType: text, int, float, bool, or JSON.
- Field Extension
- Entity-type-specific behavior applied around field type processing.
- Locale Key
- The locale component of a custom field value identity; the default is an empty string.
- Entity Type
- Stable string key stored in custom_fields.entity_type and custom_field_values.entity_type; one model class maps to one entity_type.
- Has Custom Fields
- Eloquent trait that binds a model to global custom_field_values via customFieldEntityType().
Structure
- Definition Tables
- Fieldscustom_fields
- Translationscustom_field_translations
- Optionscustom_field_options
- Value Tablecustom_field_values
- Services
- RegistryCustomFieldEntityRegistry
- CacheCustomFieldDefinitionCache
- PipelineValuePipeline
- Type RegistryFieldTypes/FieldTypeRegistry
- ExtensionsCustomFieldExtensionRegistry
- FormsEav/Filament/
Facts
- custom_fields stores entity type, name, type, data, sort, and activity/requirement/system flags; translations and options are separate child tables. authoritative
- custom_field_values is unique by field_id, entity_type, entity_id, and locale. authoritative
- custom_field_values has value_text, value_int, value_float, value_bool, and value_json columns. authoritative
- The value table indexes entity lookup, locale, and typed filtering by entity type, field, locale, and each scalar value; text index creation is dialect-specific. authoritative
- The field type directory implements text, long text, number, boolean, date, datetime, select, multiselect, and file types. authoritative
- ValuePipeline runs extensions before store, field type store, then extensions after store. authoritative
- ValuePipeline runs field type load before extension afterLoad processing. authoritative
- ValuePipeline combines FieldType validation errors with each registered extension's validation errors. authoritative
- HasCustomFields requires customFieldEntityType(); optional customFieldEntityLabel() controls admin registry visibility; default field/value models are App\Models\CustomField and App\Models\CustomFieldValue. authoritative
- EavServiceProvider discovers every app/Models and modules/*/Models class that uses HasCustomFields and registers it through CustomFieldEntityRegistry::registerFromModel during boot. authoritative
- When customFieldEntityLabel returns null, registerFromModel skips the entity and it does not appear in CustomFieldResource entity selectors. authoritative
- User uses HasCustomFields with entity_type user and label Пользователь (user); values persist in custom_field_values with entity_type user and entity_id equal to users.id. authoritative
- HasCustomFields exposes getCustomFieldValue, setCustomFieldValue, setCustomFieldValues, getAllCustomFieldValues, loadCustomFields, customFieldValues relation, and whereField or whereFields query scopes. authoritative
- CustomFieldsFormHelper builds Filament schema by entity_type; Edit pages use fillCustomFieldsData and extractFromData plus saveValues after the parent record is saved. authoritative
How-to guides
Connect Model
- add HasCustomFields trait to the Eloquent model
- implement customFieldEntityType returning a unique stable snake-case key
- implement customFieldEntityLabel when the entity should appear in CustomFieldResource selectors
- rely on EavServiceProvider auto-discovery or call CustomFieldEntityRegistry::registerFromModel with module name for module-owned entities
- create field definitions in admin with matching entity_type
- wire Filament resource form and save hooks through CustomFieldsFormHelper when admin editing is required
Add Field Type
- implement the FieldType contract for validation, storage, loading, and form behavior
- register it in FieldTypeRegistry
- verify typed persistence and query/index implications
- add or update the Filament form integration
Add Entity Extension
- implement a custom field extension for the target entity type
- register it in CustomFieldExtensionRegistry
- verify beforeStore, afterStore, afterLoad, and validation behavior
Rules
Architectural rule
Do not write typed value columns directly when the field has a registered type or extension; use the EAV value pipeline.
Architectural rule
Do not assume global EAV tables apply to Shop; Shop has a separate EAV schema and extension.
Examples
User Model
use App\Services\Eav\Traits\HasCustomFields;
class User extends Authenticatable
{
use HasCustomFields;
public function customFieldEntityType(): string
{
return 'user';
}
public function customFieldEntityLabel(): ?string
{
return 'Пользователь (user)';
}
}
app/Models/User.php
Read Value
$bio = $user->getCustomFieldValue('bio');
$users = User::whereField('department', 'sales')->get();
app/Services/Eav/Traits/HasCustomFields.php
Filament User Form
CustomFieldsFormHelper::buildSchema('user', $record)
CustomFieldsFormHelper::fillCustomFieldsData($record, $data)
CustomFieldsFormHelper::saveValues($record, 'user', $cfData)
app/Filament/Admin/Resources/UserResource.php