{"id":256,"date":"2025-08-27T16:27:50","date_gmt":"2025-08-27T16:27:50","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=256"},"modified":"2025-08-27T16:27:52","modified_gmt":"2025-08-27T16:27:52","slug":"building-a-team-management-system-with-laravel-roles-permissions","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/building-a-team-management-system-with-laravel-roles-permissions\/","title":{"rendered":"Building a Team Management System with Laravel Roles &amp; Permissions"},"content":{"rendered":"\n<p>Many modern SaaS applications need <strong>team-based access control<\/strong>. For example, a user might be an <code>admin<\/code> in one team but just a <code>member<\/code> in another. In <strong>Laravel 12<\/strong>, you can achieve this by enabling <strong>team support in Spatie Permissions<\/strong> and building a management UI for teams, members, and roles.<\/p>\n\n\n\n<p>In this guide, we\u2019ll create a <strong>Team Management System<\/strong> where users can belong to multiple teams, each with its own roles and permissions. We\u2019ll enable Spatie\u2019s team mode, update our models, enforce team roles with middleware, and build a UI to manage team members.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1 &#8211; Enabling Teams in Spatie Permissions<\/strong><\/h2>\n\n\n\n<p>The <a href=\"https:\/\/spatie.be\/docs\/laravel-permission\">Spatie Laravel Permission<\/a> package supports <strong>teams<\/strong> (multi-tenancy). By default it\u2019s off, but you can enable it in the config file. This ensures that all roles and permissions are scoped to a specific team.<\/p>\n\n\n\n<p><strong>Fresh installation<\/strong>: open <code>config\/permission.php<\/code> and enable teams:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-string\">'teams'<\/span> =&gt; <span class=\"hljs-keyword\">true<\/span>,\n<span class=\"hljs-string\">'team_foreign_key'<\/span> =&gt; <span class=\"hljs-string\">'team_id'<\/span>,<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then run the migrations. Spatie will automatically add <code>team_id<\/code> columns to its tables.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<p><strong>If Spatie was already installed<\/strong> without teams, you can still enable it:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Set <code>'teams' =&gt; true<\/code> in <code>config\/permission.php<\/code>.<\/li>\n<li>Create a migration to add <code>team_id<\/code> to the <code>model_has_roles<\/code> and <code>model_has_permissions<\/code> tables.<\/li>\n<\/ul>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">php<\/span> <span class=\"hljs-selector-tag\">artisan<\/span> <span class=\"hljs-selector-tag\">make<\/span><span class=\"hljs-selector-pseudo\">:migration<\/span> <span class=\"hljs-selector-tag\">add_team_id_to_permission_tables<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ database\/migrations\/xxxx_xx_xx_add_team_id_to_permission_tables.php<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Migrations<\/span>\\<span class=\"hljs-title\">Migration<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Schema<\/span>\\<span class=\"hljs-title\">Blueprint<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Schema<\/span>;\n\n<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Migration<\/span> <\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">up<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span> <\/span>{\n        Schema::table(<span class=\"hljs-string\">'model_has_roles'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Blueprint $table)<\/span> <\/span>{\n            $table-&gt;unsignedBigInteger(<span class=\"hljs-string\">'team_id'<\/span>)-&gt;nullable()-&gt;index();\n        });\n\n        Schema::table(<span class=\"hljs-string\">'model_has_permissions'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Blueprint $table)<\/span> <\/span>{\n            $table-&gt;unsignedBigInteger(<span class=\"hljs-string\">'team_id'<\/span>)-&gt;nullable()-&gt;index();\n        });\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">down<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span> <\/span>{\n        Schema::table(<span class=\"hljs-string\">'model_has_roles'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Blueprint $table)<\/span> <\/span>{\n            $table-&gt;dropColumn(<span class=\"hljs-string\">'team_id'<\/span>);\n        });\n        Schema::table(<span class=\"hljs-string\">'model_has_permissions'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Blueprint $table)<\/span> <\/span>{\n            $table-&gt;dropColumn(<span class=\"hljs-string\">'team_id'<\/span>);\n        });\n    }\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Once migrated, all role and permission checks will require a <code>team<\/code> context parameter.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2 &#8211; Updating Models<\/strong><\/h2>\n\n\n\n<p>Create a <code>Team<\/code> model and link it with users. Users can belong to multiple teams with different roles.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">php<\/span> <span class=\"hljs-selector-tag\">artisan<\/span> <span class=\"hljs-selector-tag\">make<\/span><span class=\"hljs-selector-pseudo\">:model<\/span> <span class=\"hljs-selector-tag\">Team<\/span> <span class=\"hljs-selector-tag\">-m<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Models\/User.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Auth<\/span>\\<span class=\"hljs-title\">User<\/span> <span class=\"hljs-title\">as<\/span> <span class=\"hljs-title\">Authenticatable<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Eloquent<\/span>\\<span class=\"hljs-title\">Relations<\/span>\\<span class=\"hljs-title\">BelongsToMany<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">User<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Authenticatable<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">teams<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">BelongsToMany<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">$this<\/span>-&gt;belongsToMany(Team::class)\n                    -&gt;withPivot(<span class=\"hljs-string\">'role'<\/span>)\n                    -&gt;withTimestamps();\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Models\/Team.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Eloquent<\/span>\\<span class=\"hljs-title\">Model<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Eloquent<\/span>\\<span class=\"hljs-title\">Relations<\/span>\\<span class=\"hljs-title\">BelongsToMany<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Team<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Model<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">protected<\/span> $fillable = &#91;<span class=\"hljs-string\">'name'<\/span>];\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">users<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">BelongsToMany<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">$this<\/span>-&gt;belongsToMany(User::class)\n                    -&gt;withPivot(<span class=\"hljs-string\">'role'<\/span>)\n                    -&gt;withTimestamps();\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3 &#8211; Assigning Roles with Team Context<\/strong><\/h2>\n\n\n\n<p>When teams are enabled, you must always pass the team when assigning or checking roles and permissions. For example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">$team = Team::find(<span class=\"hljs-number\">1<\/span>);\n$user = User::find(<span class=\"hljs-number\">10<\/span>);\n\n<span class=\"hljs-comment\">\/\/ Assign role in the context of a team<\/span>\n$user-&gt;assignRole(<span class=\"hljs-string\">'admin'<\/span>, $team);\n\n<span class=\"hljs-comment\">\/\/ Check role in a team<\/span>\n<span class=\"hljs-keyword\">if<\/span> ($user-&gt;hasRole(<span class=\"hljs-string\">'admin'<\/span>, $team)) {\n    <span class=\"hljs-comment\">\/\/ user is an admin of this team<\/span>\n}\n\n<span class=\"hljs-comment\">\/\/ Check permission in a team<\/span>\n<span class=\"hljs-keyword\">if<\/span> ($user-&gt;can(<span class=\"hljs-string\">'edit posts'<\/span>, $team)) {\n    <span class=\"hljs-comment\">\/\/ user can edit posts, but only within this team<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Without passing the team parameter, the check will fail, because roles and permissions are always scoped by team once this mode is enabled.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4 &#8211; Middleware for Team Access<\/strong><\/h2>\n\n\n\n<p>To enforce team-based roles, we can create middleware that validates the user\u2019s role in the current team context.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">php<\/span> <span class=\"hljs-selector-tag\">artisan<\/span> <span class=\"hljs-selector-tag\">make<\/span><span class=\"hljs-selector-pseudo\">:middleware<\/span> <span class=\"hljs-selector-tag\">CheckTeamRole<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Middleware\/CheckTeamRole.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Middleware<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Closure<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Request<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">CheckTeamRole<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handle<\/span><span class=\"hljs-params\">(Request $request, Closure $next, $role)<\/span>\n    <\/span>{\n        $team = $request-&gt;route(<span class=\"hljs-string\">'team'<\/span>);\n\n        <span class=\"hljs-keyword\">if<\/span> (! $request-&gt;user()-&gt;hasRole($role, $team)) {\n            abort(<span class=\"hljs-number\">403<\/span>, <span class=\"hljs-string\">'Unauthorized team access.'<\/span>);\n        }\n\n        <span class=\"hljs-keyword\">return<\/span> $next($request);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now in your routes:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Route::middleware(&#91;<span class=\"hljs-string\">'auth'<\/span>,<span class=\"hljs-string\">'team.role:admin'<\/span>])-&gt;group(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n    Route::get(<span class=\"hljs-string\">'\/teams\/{team}\/settings'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">($team)<\/span> <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"Team Settings for team #{$team-&gt;id}\"<\/span>;\n    });\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Only users with the <code>admin<\/code> role in that specific team can access the settings.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5 &#8211; UI for Managing Teams &amp; Members<\/strong><\/h2>\n\n\n\n<p>Finally, let\u2019s add a management UI where team admins can invite members and assign them roles.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">TeamController<\/span>;\n\nRoute::middleware(&#91;<span class=\"hljs-string\">'auth'<\/span>])-&gt;group(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n    Route::resource(<span class=\"hljs-string\">'teams'<\/span>, TeamController::class);\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/TeamController.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">Team<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">User<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Request<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">TeamController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">show<\/span><span class=\"hljs-params\">(Team $team)<\/span>\n    <\/span>{\n        $users = $team-&gt;users;\n        <span class=\"hljs-keyword\">return<\/span> view(<span class=\"hljs-string\">'teams.show'<\/span>, compact(<span class=\"hljs-string\">'team'<\/span>,<span class=\"hljs-string\">'users'<\/span>));\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">addMember<\/span><span class=\"hljs-params\">(Request $request, Team $team)<\/span>\n    <\/span>{\n        $user = User::where(<span class=\"hljs-string\">'email'<\/span>,$request-&gt;email)-&gt;firstOrFail();\n        $user-&gt;assignRole(<span class=\"hljs-string\">'member'<\/span>, $team);\n        <span class=\"hljs-keyword\">return<\/span> back()-&gt;with(<span class=\"hljs-string\">'status'<\/span>,<span class=\"hljs-string\">'Member added!'<\/span>);\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateMemberRole<\/span><span class=\"hljs-params\">(Request $request, Team $team, User $user)<\/span>\n    <\/span>{\n        $user-&gt;syncRoles(&#91;$request-&gt;role], $team);\n        <span class=\"hljs-keyword\">return<\/span> back()-&gt;with(<span class=\"hljs-string\">'status'<\/span>,<span class=\"hljs-string\">'Role updated!'<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Example Blade view (<code>resources\/views\/teams\/show.blade.php<\/code>):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\">@extends('layouts.app')\n\n@section('content')\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"container\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>{{ $team-&gt;name }} Members<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n    @foreach($users as $user)\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n        {{ $user-&gt;name }} - Role: {{ $user-&gt;roles-&gt;pluck('name')-&gt;first() }}\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">method<\/span>=<span class=\"hljs-string\">\"POST\"<\/span> <span class=\"hljs-attr\">action<\/span>=<span class=\"hljs-string\">\"{{ route('teams.updateMemberRole', &#91;$team, $user]) }}\"<\/span>&gt;<\/span>\n          @csrf\n          @method('PUT')\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">select<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"role\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"member\"<\/span>&gt;<\/span>Member<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">option<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">\"admin\"<\/span>&gt;<\/span>Admin<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">option<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">select<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"btn btn-sm btn-primary\"<\/span>&gt;<\/span>Update<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n    @endforeach\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n@endsection<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This UI allows admins to add members and change their team-specific roles easily.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>We built a <strong>Team Management System in Laravel 12<\/strong> using Spatie\u2019s team feature. We enabled team support, added migrations, scoped role checks to teams, created middleware for team access, and built a UI for managing team members and their roles. With this approach, users can belong to multiple teams with different roles in each \u2014 a must for SaaS applications.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">What\u2019s Next<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/how-to-create-a-multi-level-role-and-permission-system-in-laravel\">How to Create a Multi-Level Role &amp; Permission System in Laravel<\/a><\/li>\n<li><a href=\"\/blog\/laravel-middleware-for-role-based-route-protection\">Laravel Middleware for Role-Based Route Protection<\/a><\/li>\n<li><a href=\"\/blog\/creating-a-role-specific-dashboard-in-laravel-12\">Creating a Role-Specific Dashboard in Laravel 12<\/a><\/li>\n<\/ul>\n\n","protected":false},"excerpt":{"rendered":"<p>Many modern SaaS applications need team-based access control. For example, a user might be an admin in one team but just a member in another. In Laravel 12, you can achieve this by enabling team support in Spatie Permissions and building a management UI for teams, members, and roles. In this guide, we\u2019ll create a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":260,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[23,16,15,17,34],"class_list":["post-256","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-access-control","tag-permissions","tag-roles","tag-roles-and-permissions","tag-teams"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/256","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/comments?post=256"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/256\/revisions"}],"predecessor-version":[{"id":259,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/256\/revisions\/259"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/260"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=256"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=256"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=256"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}