Authorization is one of the most important responsibilities of any modern web application. Once users are authenticated, the next crucial step is defining what each user is allowed to do. In Laravel, authorization can be handled using Gates or Policies. While Gates are perfect for simple checks, Policies are designed specifically for complex, model-based authorization. A Policy acts like a dedicated authorization class where you define permissions for actions on a specific model such as viewing, creating, updating, or deleting a resource.
This 3000-word guide will walk you through every aspect of Laravel Policies, including what they are, why they are essential, how to create them, how they work internally, how to register and call them, how to integrate them with controllers and views, how to use helper methods, how to handle authorization failures, and how to combine Policies with roles and permissions.
By the end of this article, you will understand the full power of Laravel’s Policy system and how to use it for scalable and maintainable authorization logic.
Understanding the Need for Policies in Laravel
Policies organize authorization logic based on models. For example, if your application manages Posts, Comments, Products, Orders, or Users, each resource may require complex permission rules. Policies help centralize and structure these rules.
A Post model might need the following logic:
- A user can only update their own posts
- An admin can update any post
- A user can only delete posts they created
- A user can view only posts marked as public or posts they own
- A user with a specific role can publish posts
Instead of writing these checks inside controllers, routes, or views, Policies store them in one dedicated location.
This promotes:
- Cleaner code
- Better maintainability
- Strong separation of concerns
- Easier testing
- Scalability as the application grows
Policies encourage writing authorization logic in an organized, reusable, and readable structure.
What Exactly Is a Policy in Laravel
A Policy is a PHP class containing authorization methods. Each method represents permission for a specific action on a model.
Typical Policy methods:
view
create
update
delete
restore
forceDelete
Each method returns true or false depending on whether the user is allowed to perform the action.
Example:
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
This checks whether the logged-in user is the owner of the post.
Creating a Policy With Artisan
Laravel provides a simple command to create a policy:
php artisan make:policy PostPolicy --model=Post
This generates:
app/Policies/PostPolicy.php
Inside the file, Laravel automatically includes methods for all standard actions:
public function view(User $user, Post $post) {}
public function create(User $user) {}
public function update(User $user, Post $post) {}
public function delete(User $user, Post $post) {}
This is a full structured class ready for customization.
Understanding the Generated Policy Structure
The default structure looks like:
class PostPolicy
{
public function viewAny(User $user) {}
public function view(User $user, Post $post) {}
public function create(User $user) {}
public function update(User $user, Post $post) {}
public function delete(User $user, Post $post) {}
public function restore(User $user, Post $post) {}
public function forceDelete(User $user, Post $post) {}
}
Each method has a clear responsibility.
For example:
- viewAny = permission to view a list of posts
- view = permission to view a specific post
- create = permission to create a post
- update = permission to edit a post
- delete = permission to remove a post
You can remove unused methods or add your own if needed.
How Laravel Automatically Associates a Policy With a Model
When you create a Policy using:
php artisan make:policy PostPolicy --model=Post
Laravel automatically registers the policy in:
app/Providers/AuthServiceProvider.php
Example registration:
protected $policies = [
Post::class => PostPolicy::class,
];
This tells Laravel:
Whenever authorization is requested for the Post model, use the PostPolicy class.
This automatic mapping is how Laravel knows what Policy to apply.
Writing Authorization Logic Inside Policy Methods
Let us explore examples for each type of action.
View Policy
public function view(User $user, Post $post)
{
return $post->is_public || $user->id === $post->user_id;
}
Allows viewing if the post is public or owned.
Create Policy
public function create(User $user)
{
return $user->role === 'author';
}
Allows creating new posts only for authors.
Update Policy
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
Only the post owner can update.
Delete Policy
public function delete(User $user, Post $post)
{
return $user->id === $post->user_id || $user->role === 'admin';
}
Owners and admins can delete posts.
These examples demonstrate how Policies allow writing expressive authorization logic.
Using Policies in Controllers
Inside controllers, you authorize actions using the authorize method.
Example:
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$post->update($request->all());
return redirect()->back();
}
If the user is not allowed, Laravel automatically throws:
403 Unauthorized
Short syntax:
$this->authorize('delete', $post);
This makes controller logic clean and readable.
Using Policies in Routes
You can also authorize directly in route definitions:
Route::put('/posts/{post}', function (Post $post) {
$this->authorize('update', $post);
});
Or using middleware:
Route::delete('/posts/{post}', function (Post $post) {
//
})->middleware('can:delete,post');
Using Policies in Blade Views
Blade provides helpful directives for policy-based checks.
Example:
@can('update', $post)
<a href="/posts/{{ $post->id }}/edit">Edit</a>
@endcan
Disable buttons for unauthorized users:
@cannot('delete', $post)
<p>You cannot delete this post.</p>
@endcannot
Show content only if allowed:
@can('view', $post)
<h1>{{ $post->title }}</h1>
@endcan
Blade syntax makes authorization easy within views.
Authorizing Actions Without a Model Instance
Sometimes, you authorize an action that is not related to a specific record.
For example, creating a post:
$this->authorize('create', Post::class);
In Blade:
@can('create', App\Models\Post::class)
<a href="/posts/create">Add New Post</a>
@endcan
This checks the create method in the Policy.
Using Helper Functions for Authorization
Laravel offers helper methods:
Gate::allows('update-post', $post)
Gate::denies('update-post', $post)
Or inside policies:
$user->can('update', $post)
$user->cannot('update', $post)
These simple checks are useful in custom logic.
Handling Authorization Failures
If authorization fails using authorize(), Laravel throws:
Illuminate\Auth\Access\AuthorizationException
You can catch it:
try {
$this->authorize('update', $post);
} catch (\Exception $e) {
return redirect()->back()->withErrors('Not allowed');
}
Or customize the error view.
Combining Policies With Roles and Permissions
If your app uses roles (admin, editor, author, viewer), Policies can include role logic.
Example:
if ($user->role === 'admin') {
return true;
}
Or for Spatie Permission package:
if ($user->can('edit posts')) {
return true;
}
This is powerful for complex authorization setups.
Writing Advanced Policy Conditions
You can implement multi-layer checks:
public function update(User $user, Post $post)
{
if ($user->role === 'admin') {
return true;
}
if ($post->locked) {
return false;
}
return $user->id === $post->user_id;
}
Conditions can be based on:
- model fields
- user roles
- time of the day
- subscription status
- content ownership
Policies allow any level of complexity.
Policy Method Naming Conventions
Policies use predefined method names:
viewAny
view
create
update
delete
restore
forceDelete
You can also create custom actions:
public function publish(User $user, Post $post)
{
return $user->role === 'editor';
}
Use:
$this->authorize('publish', $post);
Policy Auto-Discovery
Laravel auto-discovers policies if:
- Model is in app/Models
- Policy is in app/Policies
- Follows naming: PostPolicy for Post
Then manual registration is not required.
Multiple Actions in One Policy
A single Policy can handle multiple related tasks:
publish
unpublish
feature
archive
pin
approve
reject
All inside one PostPolicy.
This prevents scattered authorization code.
Policies and Soft Deletes
You can create rules for restore and forceDelete:
public function restore(User $user, Post $post)
{
return $user->role === 'admin';
}
public function forceDelete(User $user, Post $post)
{
return $user->role === 'admin';
}
Soft deleted models have special authorization needs.
Testing Policies
Laravel makes policy testing simple.
Example:
$user = User::factory()->create();
$post = Post::factory()->create(['user_id' => $user->id]);
$this->assertTrue($user->can('update', $post));
Testing ensures authorization logic works before production.
Policy Organization Best Practices
Follow these guidelines:
- Use one Policy per model
- Keep logic simple
- Check user roles early
- Avoid duplicating conditions
- Use helper methods when necessary
- Document your policy rules
- Write tests for each method
Proper structure ensures easy maintenance.
When to Use Policies Instead of Gates
Use Policies when:
- Authorization applies to a specific model
- Multiple actions need rules
- You need organized logic
- You want to use create, update, delete, view patterns
- Your app grows large
Use Gates only for simple checks not tied to a model.
Common Mistakes Developers Make With Policies
Avoid the following:
- Forgetting to return a boolean
- Writing logic inside controllers instead of policies
- Using user IDs directly in controllers
- Forgetting to register custom policies
- Overcomplicating rules without separating into methods
Policies need to be clean and focused.
Real-World Example: Blog Application
A blog app may have the following rules:
- Admins can do everything
- Editors can update any post
- Authors can update only their own posts
- Guests can view only published posts
- Only admins can delete posts
- Authors cannot view drafts of others
Policies make it easy to implement such rules.
Example:
public function view(User $user, Post $post)
{
if ($user->role === 'admin') return true;
if ($post->is_published) return true;
return $user->id === $post->user_id;
}
Leave a Reply