Skip to main content

Choosing the Right Tool

Filament offers multiple ways to manage relationships. Your choice depends on:
  • The type of relationship
  • The desired user experience
  • The complexity of related data
Best for: HasMany, HasManyThrough, BelongsToMany, MorphMany, MorphToManyInteractive tables below Edit/View pages for complete CRUD operations on related records.

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.
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:
$this->getOwnerRecord()
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)

Performance Optimization

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']);
}

Bulk Action Performance

DetachBulkAction::make()
    ->chunkSelectedRecords(250)
    ->fetchSelectedRecords(false) // Skip loading records

Build docs developers (and LLMs) love