Skip to main content

BelongsToMany: Multi-Select and Relation Managers

Premium
6:06

Text Version of the Lesson

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.

We generate the Model+Migration:

php artisan make:model Tag -m

Here's the structure, with relationship right away:

Migration

Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});

app/Models/Tag.php:

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

Also, the relationship from the other way:

app/Models/Product.php:

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

Next, we generate this simple Filament Resource:

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

The result is this:

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

There are a few ways to do it.


Option 1. Just Attach: Select Multiple

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

The Full Lesson is Only for Premium Members

Want to access all of our courses? (30 h 09 min)

You also get:

55 courses
Premium tutorials
Access to repositories
Private Discord
Get Premium for $129/year or $29/month

Already a member? Login here

Comments & Discussion

P
PBTraining ✓ Link copied!

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.

J
johnogle ✓ Link copied!

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.

PK
Povilas Korop ✓ Link copied!

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.

V
Vadim ✓ Link copied!

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.

J
Joe ✓ Link copied!

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:

  1. Make a Tag model with migration and seed data
  2. Create a pivot table so that a product can have many tags
  3. Update the Tag and Product models with the relevant relationships

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 :-)

RB
Rajesh Budhathoki ✓ Link copied!

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.

A
a310832 ✓ Link copied!

same question

DU
Davlatbek Ushurbakiyev ✓ Link copied!

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.

PK
Povilas Korop ✓ Link copied!

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.

DU
Davlatbek Ushurbakiyev ✓ Link copied!

Thanks for an answer, I found solution in documentation, for this you can just do preload() method, in place where using multiple()

LA
Luis Antonio Parrado ✓ Link copied!

Chain ->preloadRecordSelect() method to Tables\Actions\AttachAction::make()

// ...
->headerActions([
Tables\Actions\CreateAction::make(),
Tables\Actions\AttachAction::make()
->preloadRecordSelect(),
])
// ...
A
Ace ✓ Link copied!

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

N
Nerijus ✓ Link copied!

Sorry, I don't exactly understand what you mean. Maybe show some demo or something.

JH
Josh Hartmann ✓ Link copied!

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:

  • Forms\Components\TextInput::make('category_tag.year')
  • Forms\Components\TextInput::make('category.year')
  • Forms\Components\TextInput::make('year')

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.

JH
Josh Hartmann ✓ Link copied!

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.

DS
Daniel Schweiger ✓ Link copied!

How can I use the same form in the relation manager as on the related resource?

N
Nerijus ✓ Link copied!
DS
Daniel Schweiger ✓ Link copied!

thank you

SS
Sven Spruijt ✓ Link copied!

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.

R
Retzko ✓ Link copied!

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!

M
Modestas ✓ Link copied!

Hi, there are a few ways to do that.

  1. Using an observer - Automatically add the missing ID on saving event
  2. Using afterSave() on your create/edit form: https://filamentphp.com/docs/3.x/panels/resources/relation-managers#importing-related-records

Both ways will check for a specific event to happen and then add the data you need to the model

R
Retzko ✓ Link copied!

Thanks, Modestas, this is very helpful!

K
karlaustriainocencio ✓ Link copied!

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(),

JH
Josh Hartmann ✓ Link copied!

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(),

L
leonvr ✓ Link copied!

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'.

PK
Povilas Korop ✓ Link copied!

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.

D
dr_capins ✓ Link copied!

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!

M
Modestas ✓ Link copied!

You can create a custom view for this and display whatever is needed inside

D
dr_capins ✓ Link copied!

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

M
Modestas ✓ Link copied!

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 structure

D
dr_capins ✓ Link copied!

First 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:

public function currentMemberships(): HasMany
{
return $this->hasMany(Membership::class)
->where('year',date('Y'));
}

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:

Schema::create('memberships', function (Blueprint $table) {
$table->id();
$table->foreignIdFor('User::class');
$table->year('year');
$table->tinyInteger('type'); //Cast con ENUM
$table->string('number');
$table->timestamps();
});

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!

M
Modestas ✓ Link copied!

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 :) )

D
dr_capins ✓ Link copied!

Thanks a lot! You're a great team!!!

D
dr_capins ✓ Link copied!

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.

AR
Amr Ragab ✓ Link copied!

Awesome, really I dont find any competitors to this package <3

JC
Jon Cody ✓ Link copied!

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?

VS
Vishal Suri ✓ Link copied!

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

A
alin ✓ Link copied!

Very nice tutorial, as always, and very useful comments. A very attentive userbase :-). 2 things.

  1. When using the relationship manager with attach/detach, after detaching a tag the respective tag does not disappear from the input itself. Is this normal behavior or am i doing something wrong?
  2. Is it normal for Filament to be so painfully slow? I read that it uses livewire. In my panel i even configured ->spa(), so it should use the navigate feature of livewire, yet it is still very very slow. Am i doing something wrong? I do not have debugbar installed, if that matters.

We'd Love Your Feedback

Tell us what you like or what we can improve

Feel free to share anything you like or dislike about this page or the platform in general.