General Rules
- Host-application policies live in
app/Policies/; package policies live in the package'ssrc/Policies/directory - Only extend
BasePolicywhen the model has its own independent CRUD in the admin frontend (registered via Laravel Front).BasePolicyvalidateshasPermissionTo($name)against permissions stored in the database — if there is no CRUD, the permission does not exist and there is no reason to create it. - Do not infer that a model lacks independent CRUD merely because a policy adds business logic such as
&& $model->can_be_deletedor&& ! $instance->is_paid. Verify route/resource evidence such asRoute::front('ModelName'), anapp/Front/Resources/ModelName.phpresource, or an explicit permission before failingextends BasePolicy. - When a model does not have its own CRUD (e.g.
BillingPrice,BillingPriceVariation), the policy handles only business logic (e.g. "cannot delete if it has children") without checking user permissions by name. Access is inherited from the parent model's permission (e.g. if the user has theplanspermission, they have access to prices and variations). - Do not register policies manually in
AppServiceProvider. Laravel autodiscovers policies that follow theApp\Policies\{Model}Policynaming convention. Package policies are the exception: autodiscovery only mapsApp\Models\XtoApp\Policies\XPolicy, so the package service provider registers them withGate::policy(). - Prefer policies for reusable authorization and access rules tied to protected models or resources. Do not create ad hoc component helpers such as
canAccessVisitor()orcanManageResource()to hold authorization logic when there is a useful policy subject; put the rule in the policy and call it through Laravel authorization (authorize,can,@can, or equivalent project helpers). - Inline checks are acceptable for local UI state, public flows, or one-off guards with no useful policy subject, but protected resource access should default to policy-backed authorization.
- When a policy enforces a persistence-sensitive business rule, pair it with an observer safety net for the same rule.
- When delete authorization has business logic (e.g. "cannot delete if it has children"), expose that logic as the
can_be_deletedaccessor on the model. The policydelete()and observerdeleting()read$model->can_be_deletedinstead of duplicating the condition.