Authorization often starts simple, but as a project grows, access rules quickly become duplicated and inconsistent across controllers, Blade views, and API endpoints. Laravel Policies solve this by providing a single, explicit place to define who can do what on a given resource.
At the beginning, many Laravel apps handle access rules with quick checks:
- an
ifin a controller, - another check in a Blade view,
- maybe a condition in a service,
- and (sometimes) a missing check on an API endpoint.
A few weeks later, the rules are duplicated, inconsistent, and hard to change safely.
Laravel Policies solve that by giving you a single, explicit place to define:
Who can do what on a given model (resource).
What is a Policy in Laravel?
A Policy is a class that groups authorization logic around a specific model.
Examples:
-
ProjectPolicy→ rules forProject -
PostPolicy→ rules forPost -
OrderPolicy→ rules forOrder
A Policy answers questions like:
- “Can this user update this project?”
- “Can they delete this post?”
- “Can they view this order?”
Instead of scattering checks everywhere, you put the rule once in the Policy, then reuse it consistently with authorize() / can().
Why Policies matter (practical reasons)
1) One source of truth
One rule = one place.
No more hunting through controllers and views to find all the conditionals.
2) Consistent across the app
The same rule applies to:
- web controllers,
- API controllers,
- Blade templates,
- jobs,
- commands.
3) Less security risk
When authorization is standardized, it’s harder to “forget” a check on one route.
4) Easier to evolve
Rules change all the time (“managers can edit too”, “only premium users can do X”).
With Policies, you update the logic once.
A simple example: “Owner can update, admin can do everything”
Let’s say a Project belongs to a user. We want:
- admins can update any project,
- owners can update their own project.
Policy
// app/Policies/ProjectPolicy.php
namespace App\Policies;
use App\Models\Project;
use App\Models\User;
class ProjectPolicy
{
public function update(User $user, Project $project): bool
{
return $user->is_admin || $project->user_id === $user->id;
}
}
Use it in a controller
// app/Http/Controllers/ProjectController.php
use App\Models\Project;
class ProjectController
{
public function update(Project $project)
{
$this->authorize('update', $project);
// ... update logic
return back();
}
}
That’s the core workflow:
- Your controller asks Laravel: “is this allowed?”
- Laravel calls the Policy method (
update) - The Policy returns
trueorfalse
How does Laravel find the right Policy?
Laravel maps models to policies.
Option A: Auto discovery
If your Policy is named like ProjectPolicy and placed in app/Policies, Laravel can often discover it automatically (depending on your version/config).
Option B: Explicit mapping
You can register Policies in your AuthServiceProvider:
// app/Providers/AuthServiceProvider.php
namespace App\Providers;
use App\Models\Project;
use App\Policies\ProjectPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Project::class => ProjectPolicy::class,
];
public function boot(): void
{
$this->registerPolicies();
}
}
Using Policies outside controllers
Policies aren’t just for controllers.
In Blade
@can('update', $project)
<a href="...">Edit</a>
@endcan
In code (services, jobs, anywhere)
if (auth()->user()->can('update', $project)) {
// allowed
}
A clean pattern for real projects
A simple rule helps:
✅ Controllers orchestrate, Policies decide
- Controllers: receive request, call
authorize, run the action. - Policies: contain the business rule of access.
- Services/UseCases: do the actual work (optional, depending on your architecture).
This keeps responsibilities clear and prevents “permission spaghetti”.
Tips for writing good Policies
- Keep methods small and explicit (
view,create,update,delete). - Prefer readable conditions over clever logic.
- Use relationships cleanly (
$project->team_id,$user->teams->contains(...)). - Consider “role + ownership + status” rules (very common in SaaS).
Quick checklist
- [ ] Every write action uses
authorize()in controllers - [ ] Policies exist for core models (Project, Post, Order…)
- [ ] Views use
@caninstead of ad-hoc checks - [ ] Mapping is configured (auto or AuthServiceProvider)
- [ ] Tests cover a few critical permissions
Wrap-up
Laravel Policies are one of the best ways to keep authorization:
- centralized (one rule, one place),
- consistent (web, API, views),
- safer (fewer forgotten checks),
- easy to evolve.
Top comments (0)