Skip to main content

Black Friday 2025! Only until December 1st: coupon FRIDAY25 for 40% off Yearly/Lifetime membership!

Read more here

Multiple Roles per User?

Premium
4:03

Currently, our project has a users.role_id column. But what if a user can have many roles instead of just one?

In this lesson, let's change a one-to-many to a many-to-many relationship.


Migration: From One Foreign Key to Pivot Table

The obvious first step is to change the migration from users.role_id to a pivot table called role_user.

Migration

Schema::table('users', function (Blueprint $table) {
$table->foreignId('role_id')->default(1)->constrained();
});
 
Schema::create('role_user', static function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->foreignId('role_id')->constrained();
});

Notice: I added the id() column as an auto-increment, but it's optional and just a personal preference.


User Model Relationship: From BelongsTo to BelongsToMany

We also changed the relationship in the Model, changing the method name from role() to roles() and removing the old fillable field.

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
// ...
 
protected $fillable = [
'name',
'email',
'password',
'role_id',
];
 
// ...
 
protected $with = [
'roles'
];
 
public function role(): BelongsTo
{
return $this->belongsTo(Role::class);
}
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}

Notice: I also added a $with array, so user would be always loaded with their roles as a relationship. This is a bit "risky" and may cause unnecessary loading if you want to have just Users on other pages of your application. So, use it with caution.


Policy: Adding Collections

Did you see that $with from the above? It allows us to have a $user->roles Collection in multiple Policy methods without running into the N+1 Query problem.

From there, we can use a Collection method contains() to check for the roles we want.

Next, we have to modify our Policy conditions to use...

The Full Lesson is Only for Premium Members

Want to access all of our courses? (31 h 16 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

WX
Wen Xuan Goh ✓ Link copied!

The Factory States don't work in the test, it tell me no role can be attach to the user.

��
Дмитрий Исаенко ✓ Link copied!

Maybe you need to create the seeder for your table? As example:

class RoleSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { Role::create(['name' => 'User']); Role::create(['name' => 'Administrator']); Role::create(['name' => 'Manager']); } }

M
M ✓ Link copied!

Perhaps you would always create new users with the User role (and then add admin/manager roles after that). This acts as some kind of default/fallback where you don't need to remember to add ->user() to each factory call.

In UserFactory:

    public function configure(): static
    {
        return $this->afterCreating(function (User $user) {
            $user->roles()->attach(Role::ROLE_USER);
        });
    }

admins/managers will then also have the user role (so they could be demoted by simply removing their roles).

M
M ✓ Link copied!

I experienced foreign key errors when deleting the user account. Adding "->onDelete('cascade')" allowed the user to be deleted.

        Schema::create('role_user', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->foreignId('role_id')->constrained();
        });