Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Custom Fields Laravel Package

relaticle/custom-fields

Laravel/Filament plugin to add dynamic custom fields to any Eloquent model without migrations. Includes 20+ field types, conditional visibility, tenant isolation, admin UI integration (forms/tables/infolists), CSV import/export, optional encryption, and extensible field types.

View on GitHub
Deep Wiki
Context7

title: Field Types description: Built-in field types and how to create custom ones navigation: icon: i-lucide-shapes

Custom Fields comes with 20+ built-in field types and allows you to create your own custom field types for specialized functionality.

Built-in Field Types

Custom Fields includes 20+ pre-configured field types:

Field Type Key Data Type Description
Text Input text Text Basic text input with validation
Email email Multi-choice Email input with validation
Phone phone Multi-choice Phone number input
Link link Multi-choice URL validation and formatting
Textarea textarea Text Multi-line text input
Rich Editor rich-editor Text WYSIWYG editor with formatting
Markdown Editor markdown-editor Text Markdown syntax with preview
Number number Numeric Numeric input with min/max validation
Currency currency Float Currency formatting with locale support
Tags Input tags-input Multi-choice Multiple tags with autocomplete
Select select Single-choice Single selection dropdown
Multi-Select multi-select Multi-choice Multiple selections
Radio radio Single-choice Single choice radio buttons
Checkbox checkbox Boolean Simple true/false toggle
Checkbox List checkbox-list Multi-choice Multiple checkboxes
Toggle toggle Boolean Switch-style toggle
Toggle Buttons toggle-buttons Single-choice Button group selection
Date date Date Date picker
Date Time date-time DateTime Date and time picker
Color Picker color-picker Text Visual color selection
File Upload file-upload String File upload with validation
Record record Multi-choice Polymorphic model lookup

Creating Custom Field Types

You have flexible options for extending Custom Fields:

  • Create entirely new field types for specialized functionality
  • Extend existing built-in field types to add custom behavior
  • Replace built-in field types by creating custom ones with the same key

This allows you to customize any field type behavior while maintaining compatibility with the existing system.

Generate a Field Type

Use the Artisan command to create a new field type:

php artisan make:field-type StarRating

This creates a complete field type class in app/Filament/FieldTypes/.

Field Type Structure

Custom field types extend BaseFieldType and use the FieldSchema system:

<?php

namespace App\Filament\FieldTypes;

use Filament\Forms\Components\Select;
use Filament\Infolists\Components\TextEntry;
use Filament\Tables\Columns\TextColumn;
use Relaticle\CustomFields\FieldTypeSystem\BaseFieldType;
use Relaticle\CustomFields\FieldTypeSystem\FieldSchema;
use Relaticle\CustomFields\Models\CustomField;
use Relaticle\CustomFields\Validation\Capabilities\MaxValueCapability;
use Relaticle\CustomFields\Validation\Capabilities\MinValueCapability;

class StarRatingFieldType extends BaseFieldType
{
    public function configure(): FieldSchema
    {
        return FieldSchema::numeric()
            ->key('acme-star-rating')
            ->label('Star Rating')
            ->icon('heroicon-o-star')
            ->formComponent(function (CustomField $customField) {
                return Select::make($customField->getFieldName())
                    ->options([
                        1 => '⭐ Poor',
                        2 => '⭐⭐ Fair',
                        3 => '⭐⭐⭐ Good',
                        4 => '⭐⭐⭐⭐ Very Good',
                        5 => '⭐⭐⭐⭐⭐ Excellent',
                    ])
                    ->native(false);
            })
            ->tableColumn(function (CustomField $customField) {
                return TextColumn::make($customField->getFieldName())
                    ->formatStateUsing(function ($state) {
                        if (!$state) return 'No rating';
                        return str_repeat('⭐', (int) $state) . " ($state/5)";
                    });
            })
            ->infolistEntry(function (CustomField $customField) {
                return TextEntry::make($customField->getFieldName())
                    ->formatStateUsing(function ($state) {
                        if (!$state) return 'No rating provided';
                        $rating = (int) $state;
                        $labels = [1 => 'Poor', 2 => 'Fair', 3 => 'Good', 4 => 'Very Good', 5 => 'Excellent'];
                        return str_repeat('⭐', $rating) . " - {$labels[$rating]} ($rating/5)";
                    });
            })
            ->priority(45)
            ->withValidationCapabilities(
                MinValueCapability::class,
                MaxValueCapability::class,
            );
    }
}

Register Your Field Type

Register your custom field type in your Filament Panel Provider:

<?php

namespace App\Providers\Filament;

use App\Filament\FieldTypes\StarRatingFieldType;
use Filament\Panel;
use Filament\PanelProvider;
use Relaticle\CustomFields\CustomFieldsPlugin;

class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->plugins([
                CustomFieldsPlugin::make()
                    ->registerFieldTypes([
                        StarRatingFieldType::class,
                    ]),
            ]);
    }
}

Extending and Replacing Field Types

You have additional flexibility with custom field types:

  • Replace built-in field types: Create a custom field type with the same key as a built-in type (e.g., file-upload) to completely override it with your own implementation and behavior

This allows you to customize or completely replace any built-in field type behavior while maintaining system compatibility.

FieldSchema API

The FieldSchema class provides a fluent API for configuring field types. Choose the appropriate factory method based on your field's data type:

Data Type Factory Methods

// Text-based fields
FieldSchema::text()       // For TEXT data type (long text)
FieldSchema::string()     // For STRING data type (short text)

// Numeric fields
FieldSchema::numeric()    // For NUMERIC data type (integers)
FieldSchema::float()      // For FLOAT data type (decimals)

// Date/time fields
FieldSchema::date()       // For DATE data type
FieldSchema::dateTime()   // For DATE_TIME data type

// Boolean fields
FieldSchema::boolean()    // For BOOLEAN data type

// Choice fields
FieldSchema::singleChoice()  // For SINGLE_CHOICE data type
FieldSchema::multiChoice()   // For MULTI_CHOICE data type

Configuration Methods

->key('field-key')                    // Unique field identifier
->label('Field Label')                // Display name
->icon('heroicon-o-star')            // Icon for field type
->priority(50)                       // Sort order (lower = first)
->formComponent($component)          // Form field component
->tableColumn($column)               // Table column component
->tableFilter($filter)               // Table filter component
->infolistEntry($entry)             // Read-only display component
->withValidationCapabilities(...)   // Validation capabilities users can configure per field
->defaultValidationRules($rules)    // Validation rules always applied to this field type
->searchable()                      // Enable globally-searchable in tables (default: true)
->sortable()                        // Enable column sorting in tables (default: true)
->filterable()                      // Enable table filter for this field type (default: false)
->encryptable()                     // Allow users to enable encryption for this field

::callout{type="info"} A field type needs both filterable() and tableFilter() for filters to appear in tables. filterable() enables the capability; tableFilter() provides the filter component. ::

Settings

Add type-specific configuration options that appear in the field editor when your field type is selected. Settings are stored per custom field instance.

withSettings() takes two arguments:

  1. A Spatie Laravel Data class defining the settings structure
  2. An array of Filament form components (or a Closure returning them) for the settings UI
use Spatie\LaravelData\Data;
use Filament\Forms\Components\TextInput;

class CurrencySettings extends Data
{
    public function __construct(
        public string $currencySymbol = '$',
        public int $decimalPlaces = 2,
    ) {}
}

// In your field type's configure() method:
return FieldSchema::float()
    ->key('acme-currency')
    ->label('Currency')
    ->withSettings(CurrencySettings::class, [
        TextInput::make('currency_symbol')
            ->label('Currency Symbol')
            ->default('$'),
        TextInput::make('decimal_places')
            ->label('Decimal Places')
            ->numeric()
            ->default(2),
    ]);

The settings form components are automatically shown/hidden based on the selected field type. Access stored settings via $customField->settings in your component closures.

Multi-Value & Constraints

Control whether users can store multiple values in a single field and enforce uniqueness.

->supportsMultiValue()
->supportsUniqueConstraint()
->defaultItemValidationRules($rules)
->requiresLookupType()

supportsMultiValue() -- Shows an "Allow Multiple Values" toggle in the field editor. When enabled by the user, the field accepts multiple values (e.g., multiple emails or phone numbers) and a "Max Values" input appears. Used by Email, Phone, Link, and Record field types.

supportsUniqueConstraint() -- Shows a "Unique per Entity Type" toggle in the field editor. When enabled by the user, a UniqueCustomFieldValue validation rule is applied to prevent duplicate values across records of the same entity type. Used by Email, Phone, Link, Text, Textarea, and Number field types.

defaultItemValidationRules(array $rules) -- Validation rules automatically applied to each individual item in a multi-value field. These are not user-configurable -- they are hardcoded per field type. Only available for MULTI_CHOICE data types; throws InvalidArgumentException otherwise.

requiresLookupType() -- Replaces the user-defined options UI with an entity type selector. The field stores references to records of the selected entity type instead of static option values. Import/export treats these as entity references. Currently used by the Record field type.

Example -- the Email field type combines these to support multiple unique emails with per-item validation:

return FieldSchema::multiChoice()
    ->key('email')
    ->supportsMultiValue()
    ->supportsUniqueConstraint()
    ->withArbitraryValues()
    ->withoutUserOptions()
    ->defaultItemValidationRules(['email', 'max:254']);

Import Customization

Control how field values appear in import templates and how raw import data is transformed before storage.

->importExample('99.99')
->importTransformer(function (mixed $state): ?float {
    // Transform raw import value into the correct storage format
})

importExample(string $example) -- Displayed as a sample value in the import template UI, helping users understand the expected format for this field type.

importTransformer(Closure $transformer) -- Receives the raw value from the import file and returns the transformed value for storage. Without a transformer, the raw value is stored as-is. The closure signature is function (mixed $state): mixed.

Example -- the Currency field type strips formatting characters on import:

return FieldSchema::float()
    ->key('currency')
    ->importExample('99.99')
    ->importTransformer(function (mixed $state): ?float {
        if (blank($state)) {
            return null;
        }

        if (is_string($state)) {
            $state = preg_replace('/[^0-9.-]/', '', $state);
        }

        return round(floatval($state), 2);
    });

Component Types

You can define components in two ways:

1. Class References (Simple)

For basic components, reference Filament classes directly:

->formComponent(TextInput::class)
->tableColumn(TextColumn::class)
->infolistEntry(TextEntry::class)

2. Closures (Flexible)

For customized components, use closures that return configured components:

->formComponent(function (CustomField $customField) {
    return TextInput::make($customField->getFieldName())
        ->label($customField->name)
        ->maxLength(255);
})

Choice Fields and Options

Choice fields (select, radio, checkboxes) can handle options in different ways:

User-Defined Options (Default)

Users define their own options when creating fields:

return FieldSchema::singleChoice()
    ->key('priority-level')
    ->formComponent(function (CustomField $customField) {
        return Select::make($customField->getFieldName());
        // Options automatically applied by the system
    });

Built-in Options

Field type provides predefined options:

return FieldSchema::singleChoice()
    ->key('priority-level')
    ->withoutUserOptions()  // Disable user options
    ->formComponent(function (CustomField $customField) {
        return Select::make($customField->getFieldName())
            ->options([
                1 => 'Low',
                2 => 'Medium',
                3 => 'High',
                4 => 'Critical',
            ]);
    });

Arbitrary Values

Accept both predefined and new user-typed values:

return FieldSchema::multiChoice()
    ->key('product-tags')
    ->withArbitraryValues()  // Allow new values
    ->formComponent(function (CustomField $customField) {
        return TagsInput::make($customField->getFieldName());
    });

Data Types

Custom Fields supports these data types for storage optimization and validation compatibility:

enum FieldDataType: string
{
    case STRING = 'string';          // Short text, URLs, identifiers
    case TEXT = 'text';              // Long text, rich content, markdown
    case NUMERIC = 'numeric';        // Integers, counts
    case FLOAT = 'float';            // Decimal numbers, currency
    case DATE = 'date';              // Date only
    case DATE_TIME = 'date_time';    // Date with time
    case BOOLEAN = 'boolean';        // True/false, checkboxes, toggles
    case SINGLE_CHOICE = 'single_choice';  // Select, radio buttons
    case MULTI_CHOICE = 'multi_choice';    // Multi-select, checkbox lists, tags
}

Best Practices

  1. Use Existing Filament Components: Build on Filament's components like Select, TextInput, etc.
  2. Follow Naming Conventions:
    • Use kebab-case for keys (e.g., star-rating, country-select)
    • Use a project prefix for custom types (e.g., acme-star-rating) to avoid conflicts with built-in types
    • Only skip the prefix when intentionally replacing a built-in type
  3. Choose the Right Data Type: Select the data type that matches how your field's values should be stored
  4. Use Closures for Flexibility: For complex components, use closure-based definitions
  5. Test Your Components: Ensure your field type works in forms, tables, and infolists
  6. Consider Validation: Only allow validation rules that make sense for your field type

Field Type Priority

Field types are ordered by priority (lower numbers appear first):

  • 10-20: Common text fields
  • 30-40: Selection fields
  • 50-60: Specialized fields
  • 70+: Advanced fields

Troubleshooting

Field Type Not Appearing

If your custom field type doesn't appear in the dropdown:

  1. Ensure your field type class extends BaseFieldType
  2. Verify the field type is registered in your panel provider
  3. Clear Laravel's cache: php artisan cache:clear
  4. Check that the configure() method returns a valid FieldSchema

Components Not Rendering

If your components don't render correctly:

  1. Verify you're using $customField->getFieldName() for field names
  2. For closure-based components, ensure closures return valid Filament components
  3. Test with simple components first before adding complexity
  4. Check that you're importing all necessary Filament component classes

Your custom field type will now appear in the field type dropdown when creating new custom fields!

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon
itsemon245/lamet
baks-dev/dashboard
amoifr/pickle-panther-bundle
make-dev/orca
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle