Managing relationships
Choosing the right tool for the job
Filament provides many ways to manage relationships in the app. Which feature you should use depends on the type of relationship you are managing, and which UI you are looking for.
Relation managers - interactive tables underneath your resource forms
NOTE
These are compatible with HasMany, HasManyThrough, BelongsToMany, MorphMany and MorphToMany relationships.
Relation managers are interactive tables that allow administrators to list, create, attach, associate, edit, detach, dissociate and delete related records without leaving the resource's Edit or View page.
Select & checkbox list - choose from existing records or create a new one
NOTE
These are compatible with BelongsTo, MorphTo and BelongsToMany relationships.
Using a select, users will be able to choose from a list of existing records. You may also add a button that allows you to create a new record inside a modal, without leaving the page.
When using a BelongsToMany relationship with a select, you'll be able to select multiple options, not just one. Records will be automatically added to your pivot table when you submit the form. If you wish, you can swap out the multi-select dropdown with a simple checkbox list. Both components work in the same way.
Repeaters - CRUD multiple related records inside the owner's form
NOTE
These are compatible with HasMany and MorphMany relationships.
Repeaters are standard form components, which can render a repeatable set of fields infinitely. They can be hooked up to a relationship, so records are automatically read, created, updated, and deleted from the related table. They live inside the main form schema, and can be used inside resource pages, as well as nesting within action modals.
From a UX perspective, this solution is only suitable if your related model only has a few fields. Otherwise, the form can get very long.
Layout form components - saving form fields to a single relationship
NOTE
These are compatible with BelongsTo, HasOne and MorphOne relationships.
All layout form components (Grid, Section, Fieldset, etc.) have a relationship() method. When you use this, all fields within that layout are saved to the related model instead of the owner's model:
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Fieldset;
Fieldset::make('Metadata')
->relationship('metadata')
->schema([
TextInput::make('title'),
Textarea::make('description'),
FileUpload::make('image'),
])
In this example, the title, description and image are automatically loaded from the metadata relationship, and saved again when the form is submitted. If the metadata record does not exist, it is automatically created.
This feature is explained more in depth in the Forms documentation. Please visit that page for more information about how to use it.
Creating a relation manager
To create a relation manager, you can use the make:filament-relation-manager command:
php artisan make:filament-relation-manager CategoryResource posts title
CategoryResourceis the name of the resource class for the owner (parent) model.postsis the name of the relationship you want to manage.titleis the name of the attribute that will be used to identify posts.
This will create a CategoryResource/RelationManagers/PostsRelationManager.php file. This contains a class where you are able to define a form and table for your relation manager:
use Filament\Forms;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Table;
public function form(Schema $schema): Schema
{
return $schema
->components([
Forms\Components\TextInput::make('title')->required(),
// ...
]);
}
public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title'),
// ...
]);
}
You must register the new relation manager in your resource's getRelations() method:
public static function getRelations(): array
{
return [
RelationManagers\PostsRelationManager::class,
];
}
Once a table and form have been defined for the relation manager, visit the Edit or View page of your resource to see it in action.
Read-only mode
Relation managers are usually displayed on either the Edit or View page of a resource. On the View page, Filament will automatically hide all actions that modify the relationship, such as create, edit, and delete. We call this "read-only mode", and it is there by default to preserve the read-only behavior of the View page. However, you can disable this behavior, by overriding the isReadOnly() method on the relation manager class to return false all the time:
public function isReadOnly(): bool
{
return false;
}
Alternatively, if you hate this functionality, you can disable it for all relation managers at once in the panel configuration:
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->readOnlyRelationManagersOnResourceViewPagesByDefault(false);
}
Unconventional inverse relationship names
For inverse relationships that do not follow Laravel's naming guidelines, you may wish to use the inverseRelationship() method on the table:
use Filament\Tables;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title'),
// ...
])
->inverseRelationship('section'); // Since the inverse related model is `Category`, this is normally `category`, not `section`.
}
Handling soft-deletes
By default, you will not be able to interact with deleted records in the relation manager. If you'd like to add functionality to restore, force-delete and filter trashed records in your relation manager, use the --soft-deletes flag when generating the relation manager:
php artisan make:filament-relation-manager CategoryResource posts title --soft-deletes
You can find out more about soft-deleting here.
Listing related records
Related records will be listed in a table. The entire relation manager is based around this table, which contains actions to create, edit, attach / detach, associate / dissociate, and delete records.
You may use any features of the Table Builder to customize relation managers.
Listing with pivot attributes
For BelongsToMany and MorphToMany relationships, you may also add pivot table attributes. For example, if you have a TeamsRelationManager for your UserResource, and you want to add the role pivot attribute to the table, you can use:
use Filament\Tables;
public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name'),
Tables\Columns\TextColumn::make('role'),
]);
}
Please ensure that any pivot attributes are listed in the withPivot() method of the relationship and inverse relationship.
Creating related records
Creating with pivot attributes
For BelongsToMany and MorphToMany relationships, you may also add pivot table attributes. For example, if you have a TeamsRelationManager for your UserResource, and you want to add the role pivot attribute to the create form, you can use:
use Filament\Forms;
use Filament\Schemas\Schema;
public function form(Schema $schema): Schema
{
return $schema
->components([
Forms\Components\TextInput::make('name')->required(),
Forms\Components\TextInput::make('role')->required(),
// ...
]);
}
Please ensure that any pivot attributes are listed in the withPivot() method of the relationship and inverse relationship.
Customizing the CreateAction
To learn how to customize the CreateAction, including mutating the form data, changing the notification, and adding lifecycle hooks, please see the Actions documentation.
Editing related records
Editing with pivot attributes
For BelongsToMany and MorphToMany relationships, you may also edit pivot table attributes. For example, if you have a TeamsRelationManager for your UserResource, and you want to add the role pivot attribute to the edit form, you can use:
use Filament\Forms;
use Filament\Schemas\Schema;
public function form(Schema $schema): Schema
{
return $schema
->components([
Forms\Components\TextInput::make('name')->required(),
Forms\Components\TextInput::make('role')->required(),
// ...
]);
}
Please ensure that any pivot attributes are listed in the withPivot() method of the relationship and inverse relationship.
Customizing the EditAction
To learn how to customize the EditAction, including mutating the form data, changing the notification, and adding lifecycle hooks, please see the Actions documentation.
Attaching and detaching records
Filament is able to attach and detach records for BelongsToMany and MorphToMany relationships.
When generating your relation manager, you may pass the --attach flag to also add AttachAction, DetachAction and DetachBulkAction to the table:
php artisan make:filament-relation-manager CategoryResource posts title --attach
Alternatively, if you've already generated your resource, you can just add the actions to the $table arrays:
use Filament\Actions\AttachAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DetachAction;
use Filament\Actions\DetachBulkAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->headerActions([
// ...
AttachAction::make(),
])
->recordActions([
// ...
DetachAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
// ...
DetachBulkAction::make(),
]),
]);
}
Preloading the attachment modal select options
By default, as you search for a record to attach, options will load from the database via AJAX. If you wish to preload these options when the form is first loaded instead, you can use the preloadRecordSelect() method of AttachAction:
use Filament\Actions\AttachAction;
AttachAction::make()
->preloadRecordSelect()
Attaching with pivot attributes
When you attach record with the Attach button, you may wish to define a custom form to add pivot attributes to the relationship:
use Filament\Actions\AttachAction;
use Filament\Forms;
AttachAction::make()
->form(fn (AttachAction $action): array => [
$action->getRecordSelect(),
Forms\Components\TextInput::make('role')->required(),
])
In this example, $action->getRecordSelect() returns the select field to pick the record to attach. The role text input is then saved to the pivot table's role column.
Please ensure that any pivot attributes are listed in the withPivot() method of the relationship and inverse relationship.
Scoping the options to attach
You may want to scope the options available to AttachAction:
use Filament\Actions\AttachAction;
use Illuminate\Database\Eloquent\Builder;
AttachAction::make()
->recordSelectOptionsQuery(fn (Builder $query) => $query->whereBelongsTo(auth()->user()))
Searching the options to attach across multiple columns
By default, the options available to AttachAction will be searched in the recordTitleAttribute() of the table. If you wish to search across multiple columns, you can use the recordSelectSearchColumns() method:
use Filament\Actions\AttachAction;
AttachAction::make()
->recordSelectSearchColumns(['title', 'description'])
Attaching multiple records
The multiple() method on the AttachAction component allows you to select multiple values:
use Filament\Actions\AttachAction;
AttachAction::make()
->multiple()
Customizing the select field in the attached modal
You may customize the select field object that is used during attachment by passing a function to the recordSelect() method:
use Filament\Actions\AttachAction;
use Filament\Forms\Components\Select;
AttachAction::make()
->recordSelect(
fn (Select $select) => $select->placeholder('Select a post'),
)
Handling duplicates
By default, you will not be allowed to attach a record more than once. This is because you must also set up a primary id column on the pivot table for this feature to work.
Please ensure that the id attribute is listed in the withPivot() method of the relationship and inverse relationship.
Finally, add the allowDuplicates() method to the table:
public function table(Table $table): Table
{
return $table
->allowDuplicates();
}
Improving the performance of detach bulk actions
By default, the DetachBulkAction will load all Eloquent records into memory, before looping over them and detaching them one by one.
If you are detaching a large number of records, you may want to use the chunkSelectedRecords() method to fetch a smaller number of records at a time. This will reduce the memory usage of your application:
use Filament\Actions\DetachBulkAction;
DetachBulkAction::make()
->chunkSelectedRecords(250)
Filament loads Eloquent records into memory before detaching them for two reasons:
- To allow individual records in the collection to be authorized with a model policy before detaching (using
authorizeIndividualRecords('delete'), for example). - To ensure that model events are run when detaching records, such as the
deletinganddeletedevents in a model observer.
If you do not require individual record policy authorization and model events, you can use the fetchSelectedRecords(false) method, which will not fetch the records into memory before detaching them, and instead will detach them in a single query:
use Filament\Actions\DetachBulkAction;
DetachBulkAction::make()
->fetchSelectedRecords(false)
Associating and dissociating records
Filament is able to associate and dissociate records for HasMany and MorphMany relationships.
When generating your relation manager, you may pass the --associate flag to also add AssociateAction, DissociateAction and DissociateBulkAction to the table:
php artisan make:filament-relation-manager CategoryResource posts title --associate
Alternatively, if you've already generated your resource, you can just add the actions to the $table arrays:
use Filament\Actions\AssociateAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DissociateAction;
use Filament\Actions\DissociateBulkAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->headerActions([
// ...
AssociateAction::make(),
])
->recordActions([
// ...
DissociateAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
// ...
DissociateBulkAction::make(),
]),
]);
}
Preloading the associate modal select options
By default, as you search for a record to associate, options will load from the database via AJAX. If you wish to preload these options when the form is first loaded instead, you can use the preloadRecordSelect() method of AssociateAction:
use Filament\Actions\AssociateAction;
AssociateAction::make()
->preloadRecordSelect()
Scoping the options to associate
You may want to scope the options available to AssociateAction:
use Filament\Actions\AssociateAction;
use Illuminate\Database\Eloquent\Builder;
AssociateAction::make()
->recordSelectOptionsQuery(fn (Builder $query) => $query->whereBelongsTo(auth()->user()))
Searching the options to associate across multiple columns
By default, the options available to AssociateAction will be searched in the recordTitleAttribute() of the table. If you wish to search across multiple columns, you can use the recordSelectSearchColumns() method:
use Filament\Actions\AssociateAction;
AssociateAction::make()
->recordSelectSearchColumns(['title', 'description'])
Associating multiple records
The multiple() method on the AssociateAction component allows you to select multiple values:
use Filament\Actions\AssociateAction;
AssociateAction::make()
->multiple()
Customizing the select field in the associate modal
You may customize the select field object that is used during association by passing a function to the recordSelect() method:
use Filament\Actions\AssociateAction;
use Filament\Forms\Components\Select;
AssociateAction::make()
->recordSelect(
fn (Select $select) => $select->placeholder('Select a post'),
)
Improving the performance of dissociate bulk actions
By default, the DissociateBulkAction will load all Eloquent records into memory, before looping over them and dissociating them one by one.
If you are dissociating a large number of records, you may want to use the chunkSelectedRecords() method to fetch a smaller number of records at a time. This will reduce the memory usage of your application:
use Filament\Actions\DissociateBulkAction;
DissociateBulkAction::make()
->chunkSelectedRecords(250)
Filament loads Eloquent records into memory before dissociating them for two reasons:
- To allow individual records in the collection to be authorized with a model policy before dissociation (using
authorizeIndividualRecords('update'), for example). - To ensure that model events are run when dissociating records, such as the
updatingandupdatedevents in a model observer.
If you do not require individual record policy authorization and model events, you can use the fetchSelectedRecords(false) method, which will not fetch the records into memory before dissociating them, and instead will dissociate them in a single query:
use Filament\Actions\DissociateBulkAction;
DissociateBulkAction::make()
->fetchSelectedRecords(false)
Viewing related records
When generating your relation manager, you may pass the --view flag to also add a ViewAction to the table:
php artisan make:filament-relation-manager CategoryResource posts title --view
Alternatively, if you've already generated your relation manager, you can just add the ViewAction to the $table->recordActions() array:
use Filament\Actions\ViewAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->columns([
// ...
])
->recordActions([
ViewAction::make(),
// ...
]);
}