A gate is a named yes/no rule, like “can this user edit this post?”, that you define once and check anywhere.
🎫Think of the ticket gates at a train station. Each gate has one simple rule: “valid ticket for this zone?” It answers yes or no, and every passenger is checked by the same rule.
Logged in doesn't mean allowed
Stop the classic bug
Without checks, any logged-in user can edit anyone's post just by changing the ID in the URL.
One source of truth
The rule lives in one place. Change it once, and every controller and view follows.
Works everywhere
The same gate can be checked from controllers, Blade, routes and tests.
Define once, check everywhere
| Method | Returns | Use when |
|---|---|---|
Gate::allows('x', $post) | true / false | You want to branch with if |
Gate::denies('x', $post) | The opposite of allows | Early-return style checks |
Gate::authorize('x', $post) | Nothing, or throws a 403 | Controllers: “stop here if not allowed” |
@can('x', $post) | Shows or hides Blade | Buttons and links in views |
Your first gates
-
Define a gate in
AppServiceProviderGates are registered in the
boot()method ofapp/Providers/AppServiceProvider.php. The first argument is always the logged-in user, and Laravel passes it in for you.<?php namespace App\Providers; use App\Models\Post; use App\Models\User; use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot(): void { Gate::define('update-post', function (User $user, Post $post) { return $user->id === $post->user_id; }); Gate::define('view-admin', function (User $user) { return $user->is_admin; }); } }Guests (not logged in) automatically fail every gate, so you don't have to handle
nullusers yourself. -
Check it with
allows/deniesUse these when you want to decide what to do yourself.
use Illuminate\Support\Facades\Gate; public function edit(Post $post) { if (Gate::denies('update-post', $post)) { abort(403, 'This is not your post.'); } return view('posts.edit', compact('post')); } -
Shorter:
Gate::authorizeauthorizedoes the check and throws a 403 if it fails, all in one line. This is the style you'll use most in controllers.public function update(Request $request, Post $post) { Gate::authorize('update-post', $post); $post->update($request->validate([ 'title' => ['required', 'max:255'], 'body' => ['required'], ])); return redirect()->route('posts.show', $post); }You can also ask the user model directly:
$request->user()->can('update-post', $post)and->cannot(...). -
Hide buttons with
@canDon't show people buttons they can't use.
@cancalls the same gate.<h1>{{ $post->title }}</h1> @can('update-post', $post) <a href="{{ route('posts.edit', $post) }}">Edit</a> @endcan @cannot('view-admin') <p>Want to become an editor? Contact us.</p> @endcannot⚠️ Check in both places@canhides the button, but someone can still send the request by hand. Alwaysauthorizein the controller too. -
Let super-admins through with
Gate::beforeA
beforecallback runs before every gate. Returntrueto allow straight away, ornullto fall through to the normal rule.public function boot(): void { Gate::before(function (User $user, string $ability) { if ($user->is_admin) { return true; // admins can do anything } // return nothing (null) → normal gate decides }); Gate::define('update-post', fn (User $user, Post $post) => $user->id === $post->user_id); }Watch out: returning
falsehere would block everyone else from everything. Returntrueor nothing at all.
Avoid this, do that
// in edit(), update() and destroy()…
if (auth()->id() !== $post->user_id) {
abort(403);
}
The same rule is copied into many places. When it changes, you'll miss one.
Gate::authorize('update-post', $post);
The rule is defined once in a gate and checked by name everywhere.
Gate::before(fn (User $user) => $user->is_admin);
For non-admins this returns false, which denies every ability before any gate runs.
Gate::before(fn (User $user) => $user->is_admin ? true : null);
Return null for everyone else so the normal rules still apply.
Gates are perfect for a handful of app-wide rules, like “view admin panel”. Once you have several rules about the same model (view, update, delete a post…), move them into a policy.
- Define an
update-postgate that only lets the author edit a post. - Use
Gate::authorizeinPostController@editand@update. - Log in as a different user and visit
/posts/1/edit. Do you see a 403 page? - Wrap the Edit button in
@canand confirm it disappears for non-authors. - Add an
is_adminboolean column, and aGate::beforethat lets admins edit everything.
Key takeaways
- A gate is a named closure that returns true/false for the current user.
- Define gates with
Gate::define()inAppServiceProvider::boot(). allows/deniesreturn booleans, andauthorizethrows a 403.@can/@cannothide UI, but always check on the server too.Gate::beforeshould returntrueornull, never a blanketfalse.