In one sentence

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.

Why it matters

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.

The big picture

Define once, check everywhere

🙋User + postAda wants to edit post #7
🎫Gate “update-post”$user->id === $post->user_id ?
✅truecarry on
⛔falsehide button / 403 Forbidden
A gate is just a closure that receives the current user (plus any extra arguments) and returns true or false.
MethodReturnsUse when
Gate::allows('x', $post)true / falseYou want to branch with if
Gate::denies('x', $post)The opposite of allowsEarly-return style checks
Gate::authorize('x', $post)Nothing, or throws a 403Controllers: “stop here if not allowed”
@can('x', $post)Shows or hides BladeButtons and links in views
Step by step

Your first gates

  1. Define a gate in AppServiceProvider

    Gates are registered in the boot() method of app/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 null users yourself.

  2. Check it with allows / denies

    Use 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'));
              }
            
  3. Shorter: Gate::authorize

    authorize does 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(...).

  4. Hide buttons with @can

    Don't show people buttons they can't use. @can calls 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

    @can hides the button, but someone can still send the request by hand. Always authorize in the controller too.

  5. Let super-admins through with Gate::before

    A before callback runs before every gate. Return true to allow straight away, or null to 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 false here would block everyone else from everything. Return true or nothing at all.

Common mistakes

Avoid this, do that

❌ Avoid

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

✅ Do

          Gate::authorize('update-post', $post);
        

The rule is defined once in a gate and checked by name everywhere.

❌ Avoid

          Gate::before(fn (User $user) => $user->is_admin);
        

For non-admins this returns false, which denies every ability before any gate runs.

✅ Do

          Gate::before(fn (User $user) => $user->is_admin ? true : null);
        

Return null for everyone else so the normal rules still apply.

💡 Pro tip: when gates get crowded

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.

🏋️ Try it yourself
  1. Define an update-post gate that only lets the author edit a post.
  2. Use Gate::authorize in PostController@edit and @update.
  3. Log in as a different user and visit /posts/1/edit. Do you see a 403 page?
  4. Wrap the Edit button in @can and confirm it disappears for non-authors.
  5. Add an is_admin boolean column, and a Gate::before that lets admins edit everything.
Recap

Key takeaways

Check yourself

Mini quiz