Name: Policies
Description: Centralised authorization logic for a given Eloquent model. Policies define per-ability access control and are enforced at the controller level.
Compatible Agents: general-purpose, backend
Tags: app/Policies/**/*.php, laravel, php, backend, policy, authorization, auth
Rules
- Policy classes live in
app/Policies/
- Naming:
PascalCase with a Policy suffix, named after the model they protect: InvoicePolicy, PostPolicy
- Policies centralise all authorization logic for a given model in one place
- Create one policy for each model
- Define one method per ability — use standard names:
viewAny, view, create, update, delete, restore, forceDelete
- Always enforce authorization at the controller level — never inside actions or services
- Laravel auto-discovers policies that follow the
ModelPolicy naming convention
- For custom locations, register manually in
AuthServiceProvider
Examples
namespace App\Policies;
use App\Models\Invoice;
use App\Models\User;
class InvoicePolicy
{
public function viewAny(User $user): bool
{
return $user->isAdmin();
}
public function view(User $user, Invoice $invoice): bool
{
return $user->id === $invoice->user_id || $user->isAdmin();
}
public function create(User $user): bool
{
return $user->hasVerifiedEmail();
}
public function update(User $user, Invoice $invoice): bool
{
return $user->id === $invoice->user_id && $invoice->isDraft();
}
public function delete(User $user, Invoice $invoice): bool
{
return $user->isAdmin();
}
}
// Usage in controller
public function update(UpdateInvoiceRequest $request, Invoice $invoice): InvoiceResource
{
$this->authorize('update', $invoice);
// ...
}
// Usage in Form Request
public function authorize(): bool
{
return $this->user()->can('update', $this->route('invoice'));
}
// Usage via route middleware
Route::put('/invoices/{invoice}', [InvoiceController::class, 'update'])
->middleware('can:update,invoice');
Anti-Patterns
- Putting authorization logic directly in controllers, actions, or models
- Creating global gates instead of model-specific policies when model-based auth is appropriate
- Not creating a policy for each model
- Putting business logic inside a policy method (belongs in Actions or Services)
- Using
return true in authorize() without documenting the intent
References
- Laravel Authorization
- Related:
Controllers/SKILL.md — the layer where policies are enforced
- Related:
FormRequests/SKILL.md — can use can() in authorize() method
1---2name: policies3description: Centralised authorization logic for a given Eloquent model. Policies define per-ability access control and are enforced at the controller level.4---5
6**Name:** Policies
7**Description:** Centralised authorization logic for a given Eloquent model. Policies define per-ability access control and are enforced at the controller level.
8**Compatible Agents:** general-purpose, backend
9**Tags:** app/Policies/**/*.php, laravel, php, backend, policy, authorization, auth
10
11## Rules
12
13- Policy classes live in `app/Policies/`
14- Naming: `PascalCase` with a `Policy` suffix, named after the model they protect: `InvoicePolicy`, `PostPolicy`
15- Policies centralise **all authorization logic** for a given model in one place
16- Create one policy for **each** model
17- Define one method per ability — use standard names: `viewAny`, `view`, `create`, `update`, `delete`, `restore`, `forceDelete`
18- Always enforce authorization at the **controller** level — never inside actions or services
19- Laravel auto-discovers policies that follow the `ModelPolicy` naming convention
20- For custom locations, register manually in `AuthServiceProvider`
21
22## Examples
23
24```php
25namespace App\Policies;
26
27use App\Models\Invoice;
28use App\Models\User;
29
30class InvoicePolicy
31{
32 public function viewAny(User $user): bool
33 {
34 return $user->isAdmin();
35 }
36
37 public function view(User $user, Invoice $invoice): bool
38 {
39 return $user->id === $invoice->user_id || $user->isAdmin();
40 }
41
42 public function create(User $user): bool
43 {
44 return $user->hasVerifiedEmail();
45 }
46
47 public function update(User $user, Invoice $invoice): bool
48 {
49 return $user->id === $invoice->user_id && $invoice->isDraft();
50 }
51
52 public function delete(User $user, Invoice $invoice): bool
53 {
54 return $user->isAdmin();
55 }
56}
57```
58
59```php
60// Usage in controller
61public function update(UpdateInvoiceRequest $request, Invoice $invoice): InvoiceResource
62{
63 $this->authorize('update', $invoice);
64 // ...
65}
66
67// Usage in Form Request
68public function authorize(): bool
69{
70 return $this->user()->can('update', $this->route('invoice'));
71}
72
73// Usage via route middleware
74Route::put('/invoices/{invoice}', [InvoiceController::class, 'update'])
75 ->middleware('can:update,invoice');
76```
77
78## Anti-Patterns
79
80- Putting authorization logic directly in controllers, actions, or models
81- Creating global gates instead of model-specific policies when model-based auth is appropriate
82- Not creating a policy for each model
83- Putting business logic inside a policy method (belongs in Actions or Services)
84- Using `return true` in `authorize()` without documenting the intent
85
86## References
87
88- [Laravel Authorization](https://laravel.com/docs/authorization)
89- Related: `Controllers/SKILL.md` — the layer where policies are enforced
90- Related: `FormRequests/SKILL.md` — can use `can()` in `authorize()` method