skip to content

Gates & Policies

Gate::define holds one-off checks and Gate::before lets a superadmin bypass everything, while policies group a model's abilities by convention. Interviewers ask when a policy beats an inline check.

on this pageshow

explore

questions

6

In Laravel, what is the difference between a gate defined with Gate::define and a policy class, and when would you choose each?

level: juniorimportance: must knowfreq 72%

answer

  1. closure versus class
  2. defined in AppServiceProvider::boot()
  3. one-off, not tied to a model
  4. policy methods named after abilities
  5. resolved by the model you pass

basics

~20 s

A gate is a named closure registered with Gate::define for one-off checks not tied to a model. A policy is a class grouping one model's abilities (view, update, delete), found from the model you pass; most model rules belong there.

solid answer

~40 s

Both answer "may this user do X?" through the same `Gate` service, so every caller — `Gate::allows`, `Gate::authorize`, the `can` middleware, `@can`, `$user->can()` — works with either. A **gate** is a closure (or `[Class, 'method']`) registered by name, usually in `AppServiceProvider::boot()`: `Gate::define('publish-issue', fn (User $user) => $user->is_admin)`. A **policy** is a class such as `ArticlePolicy` whose method names are the abilities (`view`, `update`, `delete`); Laravel picks it from the **model or class passed** as the first argument. The docs call gates a way to learn the basics and recommend policies for robust apps. Rule of thumb: an action on a model — editing an article — goes in that model's policy; a global action with no model, like opening the admin dashboard, fits a gate.

code

php · 16 lines
php
<?php

namespace App\Providers;

use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Global action, no model involved: a gate
        Gate::define('view-billing', fn (User $user) => $user->role === 'admin');
    }
}

go deeper

for a junior

Define each: a gate is a named closure, a policy is a class of abilities for one model, and both are checked through the Gate.

for a middle

Explain how the Gate picks a policy from the argument before falling back to a gate, and why an unmatched ability denies.

for a senior

Organise rules so model actions live in policies and global actions in a few gates, keeping the provider short and testable.

for a principal

Set the team convention for where authorization rules live, so reviewers can find every rule for a model in one place.

## One authorization service, two ways to register rules Laravel's authorization layer is the `Illuminate\Auth\Access\Gate` service behind the `Gate` facade. Every check — `Gate::allows()`, `Gate::authorize()`, `$user->can()`, the `can` middleware, `#[Authorize]`, `@can` — goes through it. What differs is **where the rule lives**. ## Gates A **gate** is a named callback: ```php Gate::define('open-editorial-dashboard', fn (User $user) => $user->role === 'admin'); Gate::define('publish-article', [ArticlePolicy::class, 'publish']); ``` - Registered in `AppServiceProvider::boot()`. - The first parameter is always the current user; Laravel supplies it, and callers pass only the extra arguments. - Good for **global** actions not tied to a model: viewing a dashboard, exporting a report, toggling site maintenance. - Every gate lives in one provider, which becomes a long list as an app grows. ## Policies A **policy** is a class that groups a model's abilities: ```php class ArticlePolicy { public function update(User $user, Article $article): bool { return $user->id === $article->author_id || $user->role === 'editor'; } } ``` - Generated with `php artisan make:policy ArticlePolicy --model=Article`. - Method names **are** the ability names; `Gate::authorize('update', $article)` calls `update()`. - Laravel finds the policy from the argument: an `Article` instance or the class name `Article::class`. - Resolved through the service container, so constructor injection works. - Can hold a `before()` filter for the whole class. ## How the Gate chooses When you call `Gate::allows('update', $article)`: 1. global `Gate::before` callbacks run first; 2. if the first argument maps to a policy **and** that policy has a method for the ability, the policy's `before()` and then the method run; 3. otherwise a gate defined under that ability name runs; 4. if nothing matches, the result is `null`, which is treated as **deny**. So a policy method wins over a gate with the same name when a model is passed. ## Choosing, on a publishing platform | Rule | Where | |---|---| | An editor may update any article; a writer only their own | `ArticlePolicy::update` | | A reviewer may comment on articles assigned to them | `ArticlePolicy::comment` | | Only admins may open the billing screen | `Gate::define('view-billing', ...)` | | Admins may do everything | `Gate::before` or a policy `before()` | ## Common confusions - Gates and policies are not two security systems; they are two registration styles for one. - A policy does not need registering by hand when names follow the conventions (`App\Models\Article` → `App\Policies\ArticlePolicy`). - Defining a gate called `update` does not make it apply to articles when an `ArticlePolicy::update` exists — the policy is consulted first. - Neither runs by itself: something (a call, middleware, attribute or directive) must invoke the check. ## Growing from gates to policies Many apps start with a handful of gates and move to policies as models multiply. A clean migration: 1. Generate the policy: `php artisan make:policy ArticlePolicy --model=Article`. 2. Move each model-specific gate body into the matching method (`update-article` → `update`). 3. Change callers from `Gate::allows('update-article', $article)` to `Gate::allows('update', $article)`; the ability names become the method names. 4. Delete the old gates so there is one source of truth. 5. Keep genuinely global abilities (`view-billing`, `open-editorial-dashboard`) as gates. A halfway step is `Gate::define('update-article', [ArticlePolicy::class, 'update'])`, which points an old gate name at a policy method. ## What interviewers listen for - That both styles share the `Gate` and its `before`/`after` hooks. - That policies are found from the argument, so the model (or `Article::class`) must be passed. - That the documentation itself steers robust applications towards policies. - That an undefined ability denies rather than allows.

  • If both Gate::define('update', ...) and ArticlePolicy::update exist, which runs for Gate::allows('update', $article)?
    The policy. When the first argument maps to a policy that has an `update` method, the Gate resolves the policy callback before looking at gates registered under that name. Without a model argument, or if the policy lacks the method, the gate runs.
  • How does a gate or policy method get the current user?
    The Gate resolves the currently authenticated user through its user resolver and passes it as the first argument. Callers never pass the user; to check another user, use `Gate::forUser($user)` or `$otherUser->can(...)`.

A gate is a single rule on a sign by the door; a policy is the rulebook kept for one kind of room. The same doorman reads both, and when you name a room he opens its rulebook before looking at the signs.

saying these in an interview costs you the question

  • Gates and policies are two separate authorization systems
  • You must pass the current user to Gate::allows
  • Every policy has to be registered manually in a provider
  • A gate with the same name overrides the model's policy method
  • Policies run automatically on every model query
open as a page

In Laravel 13, how do Gate::authorize, #[Authorize], the can middleware, @can and $user->can differ in what they do when a check fails?

level: middleimportance: must knowfreq 60%

basics

~10 s

Gate::authorize, the can middleware and #[Authorize] throw AuthorizationException, which becomes a 403. $user->can and Gate::allows return a bool you must act on. @can only decides what Blade renders and protects nothing on the server.

open as a page

In a Laravel policy, why do viewAny and create receive only the user while view and update also receive a model, and how do you call each?

level: juniorimportance: should knowfreq 45%

basics

~20 s

viewAny and create decide about a whole kind of model, before any instance exists, so they take only the user; you call them with the class name, Gate::authorize('create', Article::class). view and update judge one record and receive it: Gate::authorize('update', $article).

open as a page

In Laravel, how does Gate::before let an admin bypass every authorization check, and why must it return null for everyone else?

level: middleimportance: should knowfreq 52%

basics

~20 s

Gate::before callbacks run before every gate and policy; any non-null return becomes the final answer. Return true for admins and null otherwise, because returning false, or a bool like $user->isAdmin(), denies every check for non-admins.

open as a page

How does Laravel find the policy for an Eloquent model, and which wins among Gate::policy, the #[UsePolicy] attribute and naming-convention discovery?

level: middleimportance: should knowfreq 40%

basics

~10 s

Laravel checks policies registered with Gate::policy first, then a #[UsePolicy] attribute on the model, then convention: ModelNamePolicy in a Policies directory at or above the model's, e.g. App\Policies\ArticlePolicy. Gate::guessPolicyNamesUsing() replaces the convention.

open as a page

In Laravel, how do Response::deny, denyWithStatus, denyAsNotFound and Gate::inspect change what a denied authorization check tells the caller?

level: seniorimportance: should knowfreq 32%

basics

~10 s

A policy can return Illuminate\Auth\Access\Response: deny('message') keeps 403 with a custom message, denyWithStatus() picks another status, denyAsNotFound() returns 404 to hide existence. Gate::inspect() returns that Response without throwing, for reading the message.

open as a page