Back to Course |
Filament 3 From Scratch: Practical Course

BelongsToMany: Multi-Select and Relation Managers

Now, let's see how we can add data to a many-to-many relationship. Let's say that our Product may have many Tags.

For Tags, I've created a simple Model with one field, seeded a few tags, and then generated this simple Filament Resource:

php artisan make:filament-resource Tag --simple --generate

The result is this:

But, of course, it's not the topic of this lesson. What we need to do is add tags to products. Multiple tags.

The relationship in Eloquent is this:

app/Models/Product.php:

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class Product extends Model
{
// ...
 
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
}
}

There are a few ways to do it.


Option 1. Just Attach: Select Multiple

Let's add one more field in the form of ProductResource.

app/Filament/Resources/ProductResource.php:

return $form
->schema([
// ...
Forms\Components\Select::make('category_id')
->relationship('category', 'name'),
Forms\Components\Select::make('tags')
->relationship('tags', 'name')
->multiple(),
]);

Yes, that's it, all you need to do is add ->multiple() to the Select field, and it becomes a multi-select!

It also includes a search with auto-complete by default. Here's how it looks:


Option 2. Create and Attach: Relation Managers

Filament also offers a function to create a related record immediately without leaving the current form. So, if you want to create a Product and then create a Tag "on-the-fly", you can use Relation Manager.

Have you noticed that we haven't used this auto-generated method of the Resource?

app/Filament/Resources/ProductResource.php:

class ProductResource extends Resource
{
// ...
 
public static function getRelations(): array
{
return [
//
];
}
}

So yeah, this is the exact method we will fill now.

Let's generate a Relation Manager, which will contain a Table and a Form, like a Resource:

php artisan make:filament-relation-manager ProductResource tags name

We need to provide three things:

  • The name of our primary resource: ProductResource
  • The relationship name: tags
  • What "tags" field is used to identify tags: name

Here's the file that has been generated for us. It may look like a lot of code, but similarly to the Filament Resource, you wouldn't need to care about most of it. In this case, we will not edit that file at all.

app/Filament/Resources/ProductResource/RelationManagers/TagsRelationManager.php:

namespace App\Filament\Resources\ProductResource\RelationManagers;
 
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
 
class TagsRelationManager extends RelationManager
{
protected static string $relationship = 'tags';
 
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
]);
}
 
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('name'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
])
->emptyStateActions([
Tables\Actions\CreateAction::make(),
]);
}
}

Then we register that Relation Manager in the Resource method I mentioned above:

app/Filament/Resources/ProductResource.php:

class ProductResource extends Resource
{
// ...
 
public static function getRelations(): array
{
return [
RelationManagers\TagsRelationManager::class,
];
}
}

And that's it. We have our Relation Manager, which is represented by a separate CRUD table below our Product create/edit form:

So, here you can manage tags to products, edit them, and create new ones.


Attach and Detach Existing Tags

We can now add and delete new tags in the relation manager. But what if we need to attach existing tags or detach tags from products without deleting them?

When creating Relation Manager, we can pass --attach flag to add attach actions to the Relation Manager.

If you forgot to pass this flag or decided later that you need such a feature, you can manually add actions to the table in the Relation Manager.

app/Filament/Resources/ProductResource/RelationManagers/TagsRelationManager.php

class TagsRelationManager extends RelationManager
{
// ...
 
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('name'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make(),
Tables\Actions\AttachAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
Tables\Actions\DetachAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\DetachBulkAction::make(),
]),
])
->emptyStateActions([
Tables\Actions\CreateAction::make(),
]);
}
}

Now, in the table, we have additional actions.

For this feature to work, the Tag Model also needs to relation to the Products Model.

app/Models/Tag.php:

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class Tag extends Model
{
use HasFactory;
 
protected $fillable = ['name'];
 
public function products(): BelongsToMany
{
return $this->belongsToMany(Product::class);
}
}

So now we can attach existing tags or detach them without deleting them.


Show BelongsToMany in Table

The final thing in this lesson is to show product tags in the table.

Guess what? The exact syntax of TextColumn::make('[relationship].[field]') will work for both belongsTo and belongsToMany!

app/Filament/Resources/ProductResource.php:

return $table
->columns([
// ...
 
Tables\Columns\TextColumn::make('category.name'),
Tables\Columns\TextColumn::make('tags.name'),
])

Filament automatically generates the comma-separated string and shows it in the table column.


And that's it. That's all you need to know about the basics of many-to-many in Filament.

Of course, as with almost every topic in this course, there are many more options and parameters that Filament offers. Dive into the official documentation for those.