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.
This section seemed to be a little less considered around creating the pivot table and the requirement to include
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
. I worked around these and eventually got it working but just noting it since there appears to be some missing connections in this lesson. Great course though.That is true, and I do like (and miss) some of the hand-holding sometimes, but Povilas did mention that the pivot tables would not be his emphasis topic of this lesson. Probably many ways to do it, but I just added the migration:
php artisan make:migration create_product_tag
then added the middle columns like so (there are other ways):
Schema::create('product_tag', function (Blueprint $table) { $table->id(); $table->foreignId('product_id'); $table->foreignId('tag_id'); $table->timestamps(); });
Add the relationships in the models (in Product.php add):
public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class); }
add the inverse in Tag.php :
public function products(): BelongsToMany { return $this->belongsToMany(Product::class); }
Some sample data in the intermediary (pivot) table, and it worked fine.
Thanks @johnogle to provide this instruction.
@PBTraining, I've just added the "use BelongsToMany" line of code. But, as Johnogle said, in this course I'm not hand-holding for the Laravel part, as Filament is a tool ON TOP of Laravel, which implies that you already have the relationships/tables set up. The course is already quite long even without the Laravel part, and if something is not working for you, feel free to ask questions in the comments.
As always - great tutorial Povilas, thank you. And i like the text format, i think for this type of tutorials it's the best.
As for Laravel part, I would also prefer at least to see a mention of required pivot table and similar things. It's not too many extra lines of text and code, but would totally make it clear what we're working with.
I think that instead of literal hand-holding, it would be better just to give instructions as a list on what is required to move forward. So, something like:
As a bonus, it'd be nice for there to be links either to other courses or to the Laravel docs so that people can go off and fill in gaps / refresh their knowledge and then continue.
The only thing I found a bit disruptive to learning was the fact that I wasn't specifically told what I needed to do to prepare for the lesson. So then part way through, I was kinda thinking, "hmm.. I'm going to need a pivot table here"
I definitely agree with the idea of not adding lots of extra details about how to do this though as it's out of scope. But a few signposts would be nice I think :-)
Dear Povilas, the implementation for attaching existing tags is missing in the
TagsRelationManager
. While you've provided the capability to create new tags when adding a new product, the functionality to attach existing tags seems to be absent. This incompleteness in the relation manager was observed. I've also examined your source code, and it appears that this particular aspect remains the same.same question
Hi everyone, I have a question.
Select::make('tags') ->relationship('tags', 'title') ->multiple(),
When use multiple(), there is not loading tags at the beggining, for example i need select filter, and when i try to use multiple i cant choose something because it hadnt loaded.
I think it is loaded when you start typing the first symbols then it's auto-completing it. You can enable/disable that behavior with
->searchable()
from what I remember. Take a look at the docs for all Select options.Thanks for an answer, I found solution in documentation, for this you can just do preload() method, in place where using multiple()
Chain
->preloadRecordSelect()
method toTables\Actions\AttachAction::make()
How would one Edit the AttachAction? seemd like this method is missing? if I have a pivot table with attributes, I can update the pivot attributes but not the _id fields in the the pivot table for example if I have an ingredient_recipe pivot table with attributes like quantity and unit per ingredient(row) when I add the IngredientsRelationsManager to the RecipeResource it updates the ingredients.name instead of the ingredient_id, ingredient_recipe in the pivot table the attributes are updated. thanks
Sorry, I don't exactly understand what you mean. Maybe show some demo or something.
Asking for any advice anyone has. I'm struggling to fnd the answer. Using method 1 above: If I have an additional required field on the pivot table (year) that should be populated by a text field on for submission. Any ideas on how to add it into the form?
I have tried:
But it keeps throughing an error SQLSTATE[HY000]: General error: 1364 Field 'year' doesn't have a default value
Frustrating the heck out of me cause I am sure it is stupid easy to fix.
Ooooh think I found a video on it... but it uses the relation manager (option 2). That will complicate things but meh, if it works I guess. https://www.youtube.com/watch?v=ERbpyZF8G5M
Single attaching is a PITA and not really what I am after. I'd much rather just have a simple multi select field and year field in the main form but I can't seem to get that to work.
How can I use the same form in the relation manager as on the related resource?
This feature is documented under relation managers https://filamentphp.com/docs/3.x/panels/resources/relation-managers#sharing-a-resources-form-and-table-with-a-relation-manager and Povilas made a youtube video about it today https://www.youtube.com/watch?v=gHKin-a7mPY
thank you
The Relation Manager is not shown on the Create pages, only on the Edit pages. The reason apparently is that the product id needs to be known, before tags can get added through the Relation Manager.
Hi - Great instructions, as always, from Povilas! I've set up a Panel for the user's to enter their data and am looking for a way to automatically attach the authorized user to a new model. The specific scenario is User, Teacher, and School. The Teacher belongsTo a User and the Teacher has a BelongsToMany relationship with the School. When the teacher creates a new school for herself, I want that automatically attached via the school_teacher table. This can be done manually on the Edit form, but it's an unnecessary step in my business case. Any thoughts on this? Thanks!
Hi, there are a few ways to do that.
saving
eventafterSave()
on your create/edit form: https://filamentphp.com/docs/3.x/panels/resources/relation-managers#importing-related-recordsBoth ways will check for a specific event to happen and then add the data you need to the model
Thanks, Modestas, this is very helpful!
I'd like to know how to get the client_name though when I created it appears the name, I tried to edit but it appears only the id of the model. does anyone know how to do it appear the name?
// This is my code of the Select form component
Select::make('audit_id') ->label('Client') ->options(AuditSchedule::with('audit.clientCertType.client')->get()->pluck('audit.clientCertType.client.client_name', 'id')) ->searchable(),
Try this:
Select::make('audit_id')
->relationship('MODEL_RELATION_NAME', 'FIELD_NAME_TO_DISPLAY')
->label('Client')
->options(AuditSchedule::with('audit.clientCertType.client')->get()->pluck('audit.clientCertType.client.client_name', 'id'))
->searchable()\
An example of one of mine:
Select::make('discipline_id')->relationship('discipline', 'name')->searchable()->preload()->required(),
Great course! A little improvement would be to add a 'link-icon' next to the headers, so in my documentation I can link directly to for example the header 'Attach and Detach Existing Tags'.
I don't think all our headers are so important and clear so people would link to them. For some of them, yes, but if we make it in all headers for all lessons in all courses, it would not make much sense.
Really great course. A question:in the table product is possibile to show only some tags? filtered by a condition? I tried using something like: ->formatStateUsing(function ($record) { return $record->tags->filter->isCurrent()->pluck('name'); }) but not working.. I need something similar in my project, where I have to show on the table only memeberships of the current year Thanks!
You can create a custom view for this and display whatever is needed inside
You mean something similar to Creating CRM with Filament 3: Step-By-Step -> Creating Tags for Customers ?? Good idea, I haven't thought to do like this, i believed there was an easiest way.. on the query level
Yes, pretty much like that!
Of course, this can be done via query, but it's a bit too much to explain how. I can only give you a direction to use
modifyQueryUsing()
on the table itself, as anything else would require me to understand your database structureFirst of all thank you for your answers (and sorry for my english "scolastic", but here in Italy we aren't so good as in other european countries!).
I solved the problem with a workaround: I created a new relationship on the User model:
so I have a second relation with only the membership of the actual year and I used in the table instead of the normal memberships relation.
Just to let you know, the database has a normal User table, without strange "things", and a Membership table like this:
So I need in the users table to view only the users with a membership on the current year.
I don't know if my workaround is "elegant", but works, and it's reusable in other places!
This is actually a perfect workaround! This gives you more than just the table solution!
ps. Don't ever stress about your english - if we can understand you - it's GOOD! (we are also non-english speakers here :) )
Thanks a lot! You're a great team!!!
Hi laravel daily team! I disturb you with another problem, in the same I project:
I have races, and in every race a user could subscrive, but the price change as far is the race date (e.g. till 31/07 €10; from 01/08 to 31/08 € 15 etc..) So my idea is to create a tranches table, where I store expire date a cost, of corse a Race has many Tranches; and I think that Users and Tranches are connected with a many-to-many relationship (using pivot table). Here the db schema (just to explain easily): https://drawsql.app/teams/dr-capins/diagrams/teammanager
In theory everything works great, but in filament I have an Issue: I don't want to create a TrancheResurce (it doesn't make sense), so I manage tranches CRUD from RaceResource using a Relationship Manager (and it works correctly).
But now, how can I manage subscriptions to the race? The administrator needs to attach a user to the race (passing for one of the tranche model). Teorically I need a kind of belongsToManyThrough, because every race should have many users, but passing through the Tranche model. I really don't know hot to manage this in filament. Maybe with a page? But how?
The last resource for me is to change the db schema, connecting directly races, tranches and users in a 3-model-pivotTable, but I really dislike this solution, because the db will not be normalized anymore (in a pivot row of this table we will have user_id, race_id and tranche_id, but it's clear that race_id is redundant.
Do you think there should be a solution keeping the db schema? Is possible to "combine" to relationship? (I also look for the has-many-deep package of Stoudamire, but doesn't seems to be for my situation.
Thanks a lot for your attention.
Awesome, really I dont find any competitors to this package <3
Hi, I am wondering if it would be possible to create a simpler interface for adding /attaching tags (or related items) similar to how wordpress handles it. With the option to create hierarchical relationships
Sample
In many cases I think records would be predefined. And users should simply be able to connect them via a checkbox.
And if they need to add new they can do so.
I suppose the first method presented would be closer to this set up, but Im not sure if the check box options in a scollable field is an option within filament?
I faced one problem in this section, the new tag created in this section also attached with product by default and detach is seems working fine we can detach and it does not show not assigned tags to product. Any one please guide