Filament offers multiple ways to manage relationships. Your choice depends on:
The type of relationship
The desired user experience
The complexity of related data
Relation Managers
Select Fields
Repeaters
Layout Components
Best for : HasMany, HasManyThrough, BelongsToMany, MorphMany, MorphToManyInteractive tables below Edit/View pages for complete CRUD operations on related records.
Best for : BelongsTo, MorphTo, BelongsToManyChoose from existing records or create new ones in a modal.
Best for : HasMany, MorphManyCreate/edit multiple related records inline within the form.
Best for : BelongsTo, HasOne, MorphOneSave form fields directly to a single related record.
Relation Managers
Relation managers display related records in a table underneath your resource form.
Creating a Relation Manager
php artisan make:filament-relation-manager CategoryResource posts title
Arguments:
CategoryResource - Parent resource class
posts - Relationship name
title - Attribute used to identify records
This creates CategoryResource/RelationManagers/PostsRelationManager.php:
packages/panels/src/Resources/RelationManagers/RelationManager.php
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' ),
// ...
]);
}
Registering Relation Managers
public static function getRelations () : array
{
return [
RelationManagers\ PostsRelationManager :: class ,
];
}
With custom URL parameter:
public static function getRelations () : array
{
return [
'posts' => RelationManagers\ PostsRelationManager :: class ,
];
}
Relation Manager Variants
php artisan make:filament-relation-manager CategoryResource posts title --attach
For BelongsToMany and MorphToMany relationships.
With Associate/Dissociate
php artisan make:filament-relation-manager CategoryResource posts title --associate
For HasMany and MorphMany relationships.
php artisan make:filament-relation-manager CategoryResource posts title --soft-deletes
Adds restore, force delete, and trashed filter.
php artisan make:filament-relation-manager CategoryResource posts title --view
Adds a view modal for related records.
Working with Pivot Attributes
For many-to-many relationships with pivot data:
Displaying Pivot Columns
use Filament\Tables\Columns\ TextColumn ;
public function table ( Table $table ) : Table
{
return $table
-> columns ([
TextColumn :: make ( 'name' ),
TextColumn :: make ( 'role' ), // Pivot attribute
]);
}
Editing Pivot Attributes
use Filament\Forms\Components\ TextInput ;
use Filament\Schemas\ Schema ;
public function form ( Schema $schema ) : Schema
{
return $schema
-> components ([
TextInput :: make ( 'name' ) -> required (),
TextInput :: make ( 'role' ) -> required (), // Pivot attribute
]);
}
Ensure pivot attributes are listed in withPivot() on both the relationship and inverse relationship.
Attach and Detach
For BelongsToMany and MorphToMany relationships:
use Filament\Actions\ AttachAction ;
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 (),
]),
]);
}
Attaching with Pivot Data
use Filament\Actions\ AttachAction ;
use Filament\Forms\Components\ TextInput ;
AttachAction :: make ()
-> schema ( fn ( AttachAction $action ) : array => [
$action -> getRecordSelect (),
TextInput :: make ( 'role' ) -> required (),
TextInput :: make ( 'started_at' ) -> date (),
])
Scoping Attachable Records
use Illuminate\Database\Eloquent\ Builder ;
AttachAction :: make ()
-> recordSelectOptionsQuery ( fn ( Builder $query ) =>
$query -> where ( 'status' , 'active' )
)
Multiple Attachment
AttachAction :: make ()
-> multiple ()
Preload Options
AttachAction :: make ()
-> preloadRecordSelect ()
Search Multiple Columns
AttachAction :: make ()
-> recordSelectSearchColumns ([ 'title' , 'description' , 'sku' ])
Associate and Dissociate
For HasMany and MorphMany relationships:
use Filament\Actions\ AssociateAction ;
use Filament\Actions\ DissociateAction ;
use Filament\Actions\ DissociateBulkAction ;
public function table ( Table $table ) : Table
{
return $table
-> headerActions ([
AssociateAction :: make (),
])
-> recordActions ([
DissociateAction :: make (),
])
-> toolbarActions ([
BulkActionGroup :: make ([
DissociateBulkAction :: make (),
]),
]);
}
Associate actions work similarly to attach actions but update the foreign key instead of creating pivot records.
Accessing the Owner Record
Get the parent record in a relation manager:
In static methods, use callbacks:
use Filament\Forms\Components\ Select ;
use Filament\Resources\RelationManagers\ RelationManager ;
Forms\Components\ Select :: make ( 'store_id' )
-> options ( function ( RelationManager $livewire ) : array {
return $livewire -> getOwnerRecord () -> stores ()
-> pluck ( 'name' , 'id' )
-> toArray ();
})
Relation Manager Features
Read-Only Mode
On View pages, relation managers are read-only by default. Override this:
public function isReadOnly () : bool
{
return false ;
}
Or globally:
use Filament\ Panel ;
public function panel ( Panel $panel ) : Panel
{
return $panel
-> readOnlyRelationManagersOnResourceViewPagesByDefault ( false )
// ...
}
Customizing the Query
use Illuminate\Database\Eloquent\ Builder ;
public function table ( Table $table ) : Table
{
return $table
-> modifyQueryUsing ( fn ( Builder $query ) =>
$query -> where ( 'is_active' , true )
)
-> columns ([
// ...
]);
}
Grouping Relation Managers
use Filament\Resources\RelationManagers\ RelationGroup ;
public static function getRelations () : array
{
return [
RelationGroup :: make ( 'Contacts' , [
RelationManagers\ IndividualsRelationManager :: class ,
RelationManagers\ OrganizationsRelationManager :: class ,
]),
];
}
Conditional Visibility
use Illuminate\Database\Eloquent\ Model ;
public static function canViewForRecord ( Model $ownerRecord , string $pageClass ) : bool
{
return $ownerRecord -> status === Status :: Published ;
}
Combining with Form Content
Render relation managers as tabs alongside your form:
public function hasCombinedRelationManagerTabsWithContent () : bool
{
return true ;
}
Customize the form tab:
use Filament\Schemas\Components\Tabs\ Tab ;
public function getContentTabComponent () : Tab
{
return Tab :: make ( 'Details' )
-> icon ( 'heroicon-m-information-circle' );
}
Relation Pages
Alternative to relation managers using dedicated pages:
php artisan make:filament-page ManageCustomerAddresses --resource=CustomerResource --type=ManageRelatedRecords
Register it:
public static function getPages () : array
{
return [
'index' => Pages\ ListCustomers :: route ( '/' ),
'create' => Pages\ CreateCustomer :: route ( '/create' ),
'view' => Pages\ ViewCustomer :: route ( '/{record}' ),
'addresses' => Pages\ ManageCustomerAddresses :: route ( '/{record}/addresses' ),
];
}
Relation pages and relation managers serve the same purpose. Use pages when you want relationship management separate from the Edit/View page.
Sharing Resources with Relation Managers
Reuse resource definitions:
use App\Filament\Resources\Blog\Posts\ PostResource ;
public function form ( Schema $schema ) : Schema
{
return PostResource :: form ( $schema );
}
public function table ( Table $table ) : Table
{
return PostResource :: table ( $table )
-> headerActions ([
CreateAction :: make (),
]);
}
Hiding Shared Components
Hide fields/columns on the relation manager:
// In the resource form
Select :: make ( 'post_id' )
-> relationship ( 'post' , 'title' )
-> hiddenOn ( CommentsRelationManager :: class )
// In the resource table
TextColumn :: make ( 'post.title' )
-> hiddenOn ( CommentsRelationManager :: class )
Lazy Loading
Relation managers lazy-load by default. Disable:
protected static bool $isLazy = false ;
Eager Loading
public static function getGlobalSearchEloquentQuery () : Builder
{
return parent :: getGlobalSearchEloquentQuery () -> with ([ 'author' , 'category' ]);
}
DetachBulkAction :: make ()
-> chunkSelectedRecords ( 250 )
-> fetchSelectedRecords ( false ) // Skip loading records