Docs

Global EAV

uticms.eav · verified 2026-07-14

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

  1. add HasCustomFields trait to the Eloquent model
  2. implement customFieldEntityType returning a unique stable snake-case key
  3. implement customFieldEntityLabel when the entity should appear in CustomFieldResource selectors
  4. rely on EavServiceProvider auto-discovery or call CustomFieldEntityRegistry::registerFromModel with module name for module-owned entities
  5. create field definitions in admin with matching entity_type
  6. wire Filament resource form and save hooks through CustomFieldsFormHelper when admin editing is required

Add Field Type

  1. implement the FieldType contract for validation, storage, loading, and form behavior
  2. register it in FieldTypeRegistry
  3. verify typed persistence and query/index implications
  4. add or update the Filament form integration

Add Entity Extension

  1. implement a custom field extension for the target entity type
  2. register it in CustomFieldExtensionRegistry
  3. 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