Filament Master Detail is a dynamic management plugin Form Component for HasMany (1,n) and Many to Many(n,n) relationships in FilamentPHP. It allows you to add and remove related records directly within the parent form, without the need to save the parent record first. Ideal for fast and fluid data entry scenarios.
- Installation
- Basic Usage
- Common Use Cases
- Additional Features
- Full Example
- Editing Behavior
- FAQ
- Screenshots
- Changelog
- Contributing
- Security
- License
- PHP >= 8.1
- Laravel >= 10
- Filament >= 3.x
composer require rodrigofs/filament-masterdetail
Important: When using the table(...) method, it is not compatible with Filament's TextColumn or other default columns. You must exclusively use the DataColumn provided by this package.
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
use Rodrigofs\FilamentMasterDetail\Tables\Columns\DataColumn;
MasterDetail::make('items')
->relationship('items')
->schema([
TextInput::make('name')->required(),
TextInput::make('description'),
])
->table([
DataColumn::make('name'),
DataColumn::make('description'),
]);
Define the HasMany relationship in the parent model:
public function items(): HasMany
{
return $this->hasMany(Item::class);
}
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
use Rodrigofs\FilamentMasterDetail\Tables\Columns\DataColumn;
MasterDetail::make('items')
->relationship()
->schema([
Select::make('shop_product_id')
->label('Product')
->options(Product::query()->pluck('name', 'id'))
->required()
->reactive()
->afterStateUpdated(fn ($state, Set $set) => $set('price', Product::find($state)?->price ?? 0))
->distinct()
->disableOptionsWhenSelectedInSiblingRepeaterItems()
->columnSpan(['md' => 5])
->searchable(),
TextInput::make('quantity')
->label('Quantity')
->numeric()
->default(1)
->required()
->columnSpan(['md' => 2]),
TextInput::make('price')
->label('Unit Price')
->numeric()
->disabled()
->dehydrated()
->required()
->columnSpan(['md' => 3]),
])
->unique('shop_product_id')
->table([
DataColumn::make('product.name')
->relationship()
->label('Product'),
DataColumn::make('quantity')
->label('Quantity'),
DataColumn::make('price')
->label('Unit Price'),
DataColumn::make('total')
->formatStateUsing(fn ($rowLoop) => $rowLoop->price * $rowLoop->quantity)
->label('Total'),
]);
You can customize the behavior of the component and the appearance and behavior of the modal used to add, delete, and edit related records:
Disable the default Add, Delete, and Edit actions on the component:
use Rodrigofs\FilamentMasterDetail\Components\MasterDetail;
// Disable actions statically
MasterDetail::make('items')
->addable(false)
->editable(false)
->removable(false)
->schema([
// Form fields
]);
// Disable actions conditionally using closures
MasterDetail::make('items')
->addable(fn (): bool => /* condition based on $record, $get, $state, $operation and more... */ false)
->editable(fn (...): bool => /* condition based on $record, $get, $state, $operation and more... */ false)
->removable(fn (...): bool => /* condition based on $record, $get, $state, $operation and more... */ false)
->schema([
// Form fields
]);
Display the form inside a Slideover instead of a traditional modal:
use Rodrigofs\FilamentMasterDetail\Components\MasterDetail;
MasterDetail::make('items')
->slideover()
->schema([
// Form fields
]);
Define the labels for modal actions and headings:
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
MasterDetail::make('items')
->addActionLabel('Add Product')
->modalHeading('Add Product')
->modalDescription('Include a new product in this order.')
->modalSubmitActionLabel('Add')
->modalSubmitEditActionLabel('Edit')
->modalCancelActionLabel('Cancel');
Customize the modal icon and size:
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
MasterDetail::make('items')
->modalIcon('heroicon-o-plus')
->modalWidth('lg');
Prevent the modal from closing automatically after adding a record:
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
MasterDetail::make('items')
->modalPersistent();
Set a custom heading for the related records table:
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
MasterDetail::make('items')
->heading('Order Items');
Prevent specific fields from being cleared after adding a record:
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
MasterDetail::make('items')
->formExceptClear(['product_id']);
Allow data manipulation before the record is added to the table:
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
MasterDetail::make('items')
->beforeAddActionExecute(fn ($state, $set) => $set('product_id', $state));
Define custom actions in the header of the MasterDetail component:
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
MasterDetail::make('items')
->headerActions([
Action::make('reset')
->modalHeading('Are you sure?')
->modalDescription('All existing items will be removed from the order.')
->requiresConfirmation()
->color('danger')
->action(fn (Forms\Set $set) => $set('items', [])),
]);
There are many additional features available—more than can be covered at once. While I will continue to document them, I encourage you to freely explore all possibilities. Don’t hesitate to open an issue if you encounter any problems or have suggestions.
use Rodrigofs\FilamentMasterDetail\Forms\Components\MasterDetail;
MasterDetail::make('items')
->relationship()
->schema([
Select::make('product_id')
->label('Product')
->options(Product::query()->pluck('name', 'id'))
->required(),
TextInput::make('quantity')
->numeric()
->required(),
])
->table([
DataColumn::make('product.name')
->relationship()
->label('Product'),
DataColumn::make('quantity')
->label('Quantity'),
])
->addActionLabel('Add Product')
->modalHeading('Add Product')
->modalDescription('Include a new product in this order.')
->modalIcon('heroicon-o-plus')
->modalWidth('lg')
->modalSubmitActionLabel('Add')
->modalCancelActionLabel('Cancel')
->heading('Order Items')
->formExceptClear(['product_id'])
->beforeAddActionExecute(fn ($state, $set) => $set('product_id', $state))
->headerActions([
Action::make('reset')
->modalHeading('Are you sure?')
->modalDescription('All existing items will be removed from the order.')
->requiresConfirmation()
->color('danger')
->action(fn (Forms\Set $set) => $set('items', [])),
])
->slideOver();
The editing feature works for related records defined via the relationship() method on the component.
Important: This feature does not support custom statePath-based implementations that do not use the relationship() method. Relationship-based binding is required for the edit action to resolve the model instance correctly.
-
Do I need to save the parent record before adding related records? No. MasterDetail allows adding and removing related records before persisting the parent model.
-
Does it support other relationship types besides HasMany?
*Currently, only HasMany relationships are supported.* Yes. In addition to HasMany, it is now possible to create records using BelongsToMany (many-to-many) relationships. However, attaching existing related records is not yet supported. -
Is there support for editing related records?
No. Only adding and removing records is supported at the moment.Yes. You can edit related records through an edit action.
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.