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.
Custom Fields comes with 20+ built-in field types and allows you to create your own custom field types for specialized functionality.
Custom Fields includes 20+ pre-configured field types:
| Field Type | Key | Data Type | Description |
|---|---|---|---|
| Text Input | text |
Text | Basic text input with validation |
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 |
You have flexible options for extending Custom Fields:
This allows you to customize any field type behavior while maintaining compatibility with the existing system.
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/.
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 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,
]),
]);
}
}
You have additional flexibility with custom field types:
file-upload) to completely override it with your own implementation and behaviorThis allows you to customize or completely replace any built-in field type behavior while maintaining system compatibility.
The FieldSchema class provides a fluent API for configuring field types. Choose the appropriate factory method based on your field's data type:
// 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
->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.
::
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:
Closure returning them) for the settings UIuse 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.
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']);
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);
});
You can define components in two ways:
For basic components, reference Filament classes directly:
->formComponent(TextInput::class)
->tableColumn(TextColumn::class)
->infolistEntry(TextEntry::class)
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 (select, radio, checkboxes) can handle options in different ways:
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
});
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',
]);
});
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());
});
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
}
Select, TextInput, etc.kebab-case for keys (e.g., star-rating, country-select)acme-star-rating) to avoid conflicts with built-in typesField types are ordered by priority (lower numbers appear first):
If your custom field type doesn't appear in the dropdown:
BaseFieldTypephp artisan cache:clearconfigure() method returns a valid FieldSchemaIf your components don't render correctly:
$customField->getFieldName() for field namesYour custom field type will now appear in the field type dropdown when creating new custom fields!
How can I help you explore Laravel packages today?