Policies — authorization organized around models
Concept
Policies organize authorization logic into classes scoped to a specific Eloquent model. Each method in a policy corresponds to an ability (view, create, update, delete, restore, forceDelete) or a custom action.
Creating policies: php artisan make:policy PostPolicy --model=Post. Generates app/Policies/PostPolicy.php with stubs for all CRUD abilities.
Policy structure: The first argument is always the authenticated User. The second is the model instance (except for create which receives none, since the model doesn't exist yet).
Policy registration: Two approaches:
- Auto-discovery (Laravel 10+): Policies in
app/Policies/following theModelNamePolicyconvention are auto-discovered. - Manual: Register in
AuthServiceProvider::$policies:[Post::class => PostPolicy::class].
Using policies: Gate::authorize('update', $post), $this->authorize('update', $post) (in controllers extending Controller), $user->can('update', $post).
Policy for action without a model: $this->authorize('create', Post::class) — passes the class name instead of an instance.
@can directive with models: @can('update', $post).
Guest handling: By default, if the user is not logged in, all policy methods return false automatically. To allow guests to pass specific methods, type-hint the first argument as ?User.
Code Example
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
class PostPolicy
{
// view — can this user view this post?
public function view(User $user, Post $post): bool
{
return $post->is_published || $user->id === $post->user_id;
}
// create — no model instance (post doesn't exist yet)
public function create(User $user): bool
{
return $user->email_verified_at !== null;
}
// update
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
// delete
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id && !$post->is_published;
}
// restore (soft deletes)
public function restore(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
// Custom action
public function publish(User $user, Post $post): bool
{
return $user->id === $post->user_id && $post->isDraft();
}
// Guest access — allow guests to view published posts
public function viewPublished(?User $user, Post $post): bool
{
return $post->is_published; // user may be null (guest)
}
}
// Using in controllers
class PostController extends Controller
{
public function update(Request $request, Post $post)
{
$this->authorize('update', $post); // throws AuthorizationException → 403
// ...
}
public function store(Request $request)
{
$this->authorize('create', Post::class); // class name for model-less
// ...
}
public function publish(Post $post)
{
$this->authorize('publish', $post); // custom action
// ...
}
}
// Route model binding + policy — common pattern
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware(['auth', 'can:update,post']); // policy called via can middleware