Custom Fields Logo
3.x
Essentials

Configuration

Configure all aspects of the Custom Fields package

Overriding Models

The Custom Fields package allows you to replace the default models with your own implementations.

Registering Custom Models

Register your custom models using the CustomFields class:

use Relaticle\CustomFields\CustomFields;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        CustomFields::useCustomFieldModel(YourCustomField::class);
        CustomFields::useValueModel(YourCustomFieldValue::class);
        CustomFields::useOptionModel(YourCustomFieldOption::class);
        CustomFields::useSectionModel(YourCustomFieldSection::class);
    }
}

Date Display Formats

By default, date and date-time fields use different formats depending on context (forms use Y-m-d, tables use M j, Y, etc.). You can set a single consistent format across all Filament components.

use Relaticle\CustomFields\CustomFieldsPlugin;

CustomFieldsPlugin::make()
    ->dateDisplayFormat('m/d/Y')
    ->dateTimeDisplayFormat('m/d/Y h:i A')

Via Facade

use Relaticle\CustomFields\CustomFields;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        CustomFields::useDateDisplayFormat('m/d/Y');
        CustomFields::useDateTimeDisplayFormat('m/d/Y h:i A');
    }
}

The configured format applies to:

ComponentWithout configWith config
Form date pickersY-m-d / Y-m-d H:i:sYour format
Table columnsM j, Y / M j, Y H:iYour format
Infolist entriesY-m-d / Y-m-d H:i:sYour format
Validation messagesM j, YYour format
This only affects how dates are displayed. Storage formats (Y-m-d and Y-m-d H:i:s) remain unchanged.

Pass null to reset back to component defaults:

CustomFields::useDateDisplayFormat(null);

Configuration File

The configuration file (config/custom-fields.php) allows you to customize all aspects of the Custom Fields package. It uses modern fluent configurators for type safety and better IDE support.

Entity Configuration

Configure which models can have custom fields:

'entity_configuration' => EntityConfigurator::configure()
    ->discover(app_path('Models'))  // Auto-discover models in this path
    ->cache(true),                  // Enable caching for performance

Field Types Configuration

Control which field types are available:

'field_type_configuration' => FieldTypeConfigurator::configure()
    ->enabled([])                   // Empty = all enabled
    ->disabled(['file-upload'])     // Disable specific field types
    ->discover(true)                // Auto-discover custom field types
    ->cache(enabled: false, ttl: 3400),

Features Configuration

Configure package features using the enum-based system:

'features' => FeatureConfigurator::configure()
    ->enable(
        CustomFieldsFeature::FIELD_CONDITIONAL_VISIBILITY,
        CustomFieldsFeature::FIELD_ENCRYPTION,
        CustomFieldsFeature::FIELD_OPTION_COLORS,
        CustomFieldsFeature::UI_TABLE_COLUMNS,
        CustomFieldsFeature::UI_TOGGLEABLE_COLUMNS,
        CustomFieldsFeature::UI_TABLE_FILTERS,
        CustomFieldsFeature::SYSTEM_MANAGEMENT_INTERFACE
    )
    ->disable(
        CustomFieldsFeature::SYSTEM_MULTI_TENANCY
    ),

Management Interface

Configure the custom fields management page:

'management' => [
    'slug' => 'custom-fields',      // URL slug
    'navigation_sort' => -1,        // Navigation sort order
    'navigation_group' => true,     // Group in navigation
    'cluster' => null,              // Optional cluster assignment
],

Replacing the Management Page

The config above covers the common cases. Filament reads some things off the page class itself rather than from config — sub-navigation, breadcrumbs, header actions — so when you need one of those, register your own page instead:

use App\Filament\Pages\Settings\CustomFields;

CustomFieldsPlugin::make()
    ->managementPage(CustomFields::class)
namespace App\Filament\Pages\Settings;

use Filament\Panel;
use Filament\Pages\Enums\SubNavigationPosition;
use Relaticle\CustomFields\Filament\Management\Pages\CustomFieldsManagementPage;

class CustomFields extends CustomFieldsManagementPage
{
    protected static ?SubNavigationPosition $subNavigationPosition = SubNavigationPosition::Start;

    public static function getSlug(?Panel $panel = null): string
    {
        return 'settings/custom-fields';
    }

    public function getSubNavigation(): array
    {
        // Render this page inside your own settings navigation.
    }
}

The page must extend CustomFieldsManagementPage, and it replaces the packaged page rather than sitting alongside it — only one management page is registered on the panel, so there is no second route to the same screen.

Select Behavior

Controls when option-backed selects render a search box, and how the record-select field orders and pages its lookups:

'selects' => [
    'searchable_threshold' => 10,

    'record_lookup' => [
        'order_column' => null,
        'order_direction' => 'desc',
        'limit' => 50,
        'min_search_length' => 2,
    ],
],
KeyDefaultEffect
searchable_threshold10Select and multi-select fields render a search box only when they have more options than this. Set it to 0 to always render one.
record_lookup.order_columnnullColumn the record-select orders its initial page and search results by. null means the model's key, which is backed by the primary key index. Name a real column to override.
record_lookup.order_direction'desc'Direction for that column. The model key is always applied after it, so rows sharing a value keep a fixed order.
record_lookup.limit50Rows fetched for the initial page and for each search.
record_lookup.min_search_length2Characters required before a filtered lookup query is issued. Below it, the field shows the unfiltered first page. Both the server and the field's JavaScript read this value.
Before 3.8 every select rendered a search box, including a three-option status field. Set searchable_threshold to 0 to restore that behavior exactly.

The default exists to make the initial page deterministic without paying for it. Measured on a 50,000-row lookup table, ordering by the model key plans as an index scan and costs the same as the unordered query it replaces, while ordering by an unindexed updated_at sorts the whole tenant on every render (roughly 176x the time and 205x the buffers). If you prefer most-recently-touched-first, set order_column to 'updated_at' and index that column.

When order_column is set to updated_at and the looked-up model returns false from usesTimestamps(), the model key is used instead, so the order is always deterministic.

Database Configuration

Customize table names and paths:

'database' => [
    'migrations_path' => database_path('custom-fields'),
    'table_names' => [
        'custom_field_sections' => 'custom_field_sections',
        'custom_fields' => 'custom_fields',
        'custom_field_values' => 'custom_field_values',
        'custom_field_options' => 'custom_field_options',
    ],
    'column_names' => [
        'tenant_foreign_key' => 'tenant_id',
    ],
],

Available Features

The package supports these features that can be enabled/disabled:

FeatureDescription
FIELD_CONDITIONAL_VISIBILITYShow/hide fields based on conditions
FIELD_ENCRYPTIONEncrypt sensitive field values
FIELD_OPTION_COLORSColor-coded options for select fields
FIELD_CODE_AUTO_GENERATEAuto-generate field codes from names
FIELD_MULTI_VALUEAllow multiple values per field
FIELD_UNIQUE_VALUEEnforce unique constraint per entity type
FIELD_VALIDATION_RULESEnable validation rule configuration
UI_TABLE_COLUMNSShow custom fields as table columns
UI_TOGGLEABLE_COLUMNSAllow users to toggle column visibility
UI_TOGGLEABLE_COLUMNS_HIDDEN_DEFAULTHide toggleable columns by default
UI_TABLE_FILTERSEnable filtering by custom field values
UI_FIELD_WIDTH_CONTROLCustom field width per field
UI_SECTION_WIDTH_CONTROLSection-level layout width (25/33/50/66/75/100)
SYSTEM_MANAGEMENT_INTERFACEEnable the management interface
SYSTEM_MULTI_TENANCYEnable multi-tenant isolation
SYSTEM_SECTIONSEnable field grouping in sections
If your custom models include tenant-specific scoping logic, you'll need to register a custom tenant resolver to ensure validation works correctly.

UI_SECTION_WIDTH_CONTROL is disabled by default. Enable it by adding CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL to the ->enable(...) list in your published config. Once enabled, each SECTION and FIELDSET (not headless sections) can render at a fraction of the row width using the same CustomFieldWidth enum used for field-level width. Section widths don't need to sum to 12 — the grid wraps, and sections stack full-width on mobile.

Both section and field widths are fractions of the standard 12-column custom fields grid — 50% renders as a 6-of-12 column span. If you embed custom fields inside a grid with a different column count, the visual fraction follows that grid instead.

Configuration Examples

Restricting Field Types

Limit available field types in production:

'field_type_configuration' => FieldTypeConfigurator::configure()
    ->enabled([
        'text',
        'textarea',
        'number',
        'select',
        'checkbox',
        'date',
    ])
    ->disabled([
        'rich-editor',      // Disable rich content editors
        'markdown-editor',  // Disable markdown editor
        'file-upload',      // Disable file uploads
    ]),

Performance Configuration

Optimize for production:

'entity_configuration' => EntityConfigurator::configure()
    ->discover(app_path('Models'))
    ->cache(env('CUSTOM_FIELDS_CACHE', true)),  // Enable caching

'field_type_configuration' => FieldTypeConfigurator::configure()
    ->cache(enabled: true, ttl: 3600),          // Cache field types

Multi-Tenancy Setup

Enable tenant isolation:

'features' => FeatureConfigurator::configure()
    ->enable(
        CustomFieldsFeature::SYSTEM_MULTI_TENANCY,
        // ... other features
    ),

'database' => [
    // ... other config
    'column_names' => [
        'tenant_foreign_key' => 'tenant_id',  // Your tenant foreign key
    ],
],

Custom Tenant Resolution

If you've extended the CustomField or CustomFieldSection models with custom tenant handling (e.g., custom global scopes), register a tenant resolver to ensure validation and queries respect your custom logic:

use Relaticle\CustomFields\CustomFields;

// In your AppServiceProvider or plugin boot method
CustomFields::resolveTenantUsing(fn() => auth()->user()?->company_id);
The custom resolver takes priority over Filament's built-in tenancy, giving you complete control over tenant resolution.

Common Patterns:

// Auth-based tenancy
CustomFields::resolveTenantUsing(fn() => auth()->user()?->company_id);

// Header-based (APIs)
CustomFields::resolveTenantUsing(fn() => request()->header('X-Tenant-ID'));

// Session-based
CustomFields::resolveTenantUsing(fn() => session('current_tenant_id'));
Need help? Check that your resolver returns the correct tenant ID using TenantContextService::getCurrentTenantId() in your application.

Best Practices

Performance Optimization

  1. Enable Caching: Always enable caching in production
  2. Limit Discovery: Only discover models you need
  3. Restrict Field Types: Only enable field types you use

Security Considerations

  1. Disable Unused Features: Turn off features you don't need
  2. Restrict Field Types: Disable potentially unsafe field types like rich editors
  3. Enable Multi-Tenancy: Always enable in multi-tenant applications

Development vs Production

Use environment variables for flexible configuration:

'entity_configuration' => EntityConfigurator::configure()
    ->discover(app_path('Models'))
    ->cache(env('CUSTOM_FIELDS_CACHE', !app()->isLocal())),

'field_type_configuration' => FieldTypeConfigurator::configure()
    ->cache(enabled: env('CUSTOM_FIELDS_CACHE_TYPES', true)),
Copyright © 2026