Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Ownable Laravel Package

santigarcor/laravel-ownable

A Laravel package that adds polymorphic ownership to Eloquent models, letting any model be assigned to users or other entities. It provides traits, relationships, and helpers to query owned records and manage ownership across your application.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer and verify the version constraint matches your Laravel version (the last tagged release is from late 2023, so check composer show santigarcor/laravel-ownable after install before relying on it in a new Laravel 11/12 app):

composer require santigarcor/laravel-ownable

Add the Ownable trait to the model that represents the owner — almost always User, but it can be any model that acts as an owner (a Team, Organization, Account, etc.):

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Santigarcor\LaravelOwnable\Ownable;

class User extends Model
{
    use Ownable;
}

The first use case to wire up is authorizing an action on a child record in a controller or policy. Instead of hand-rolling $comment->post->user_id === auth()->id(), ask the owner directly:

public function update(Comment $comment)
{
    abort_unless(auth()->user()->owns($comment), 403);

    $comment->update(request()->validated());
}

If the package ships a config file (config/ownable.php), publish it early with php artisan vendor:publish --tag=ownable-config so ownership-chain behavior is visible and version-controlled from day one. If no config is published, read the trait source in vendor/santigarcor/laravel-ownable/src — it is small enough to skim in a few minutes, and you will need to know exactly which methods (owns, ownedBy, etc.) it exposes and how it resolves relations.

Implementation Patterns

Policy integration. The cleanest day-to-day use is inside a policy so ownership logic lives in one place and is reused by controllers, Blade @can, and API resources:

class CommentPolicy
{
    public function update(User $user, Comment $comment): bool
    {
        return $user->owns($comment);
    }
}

Ownership through an intermediate relation. The main reason to reach for this package over a one-line user_id comparison is indirect ownership — a Comment belongs to a Post, which belongs to a User. Verify in the source how the trait walks relations; typically you either define the ownership chain on the model or pass the resolved chain. A robust pattern is to keep a single canonical "owner" relation on each model:

class Comment extends Model
{
    public function owner(): User
    {
        return $this->post->owner;
    }
}

Then check $user->owns($comment->owner) rather than reimplementing traversal in every controller.

Guard clauses in form requests. Ownership checks pair well with authorize() in a FormRequest, keeping controllers thin:

public function authorize(): bool
{
    return $this->user()->owns($this->route('comment'));
}

Blade and API resources. Because policies call owns, you get consistent Blade behavior for free:

@can('update', $comment)
    <a href="{{ route('comments.edit', $comment) }}">Edit</a>
@endcan

Testing. Write one test per ownership shape (direct, indirect, polymorphic, unrelated owner) using factories. These tests document the package's behavior for your team and will catch a silent breakage if you later upgrade Laravel or replace the package:

it('lets the post author manage its comments', function () {
    $post    = Post::factory()->create();
    $comment = Comment::factory()->for($post)->create();

    expect($post->user->owns($comment))->toBeTrue();
});

Migration path. Since the package is small and thinly maintained, wrap usage behind your own contract (OwnershipResolver or a base Ownable trait that uses the package trait) so a future swap is a single-file change rather than a find-and-replace across policies.

Gotchas and Tips

  • Maintenance risk is the headline concern. 19 stars and a 2023 release means Laravel 11+ compatibility is not guaranteed and issues may go unanswered. Pin the version in composer.json and add a small integration test suite; treat the package as a convenience trait, not infrastructure.
  • ID type strictness. Ownership comparisons typically compare user_id values. With UUIDs or ULIDs mixed with integer keys, or when one side is a string from a request and the other an int from the DB, a loose == may pass where strict === would not. Confirm which the trait uses and cast consistently ($model->user_id vs $model->user->id).
  • Polymorphic owners. If a model can be owned by more than one type, make sure the morph_type is compared, not just the id — otherwise a Post id 5 and a Team id 5 look identical. Test this explicitly.
  • N+1 queries from relation walking. Indirect ownership checks can lazy-load a chain per record. In an index view that renders @can per row, eager-load the chain:
$comments = Comment::with('post.user')->paginate();

For large lists, prefer a query constraint (whereHas('post', fn ($q) => $q->where('user_id', $id))) over the trait, since the trait loads models into memory to answer.

  • Unpersisted models. Checking owns() on a model that has not been saved will have a null foreign key and generally return false, which can be confusing in store flows — branch on exists before authorizing.
  • Trait/method collisions. If your model already defines owns or uses another package's Ownable trait, methods silently clash. Alias on import if needed: use Ownable { owns as packageOwns; }.
  • Debugging. When an ownership check returns an unexpected false, dump the resolved foreign keys and morph types rather than the whole models:
dump($comment->user_id, $comment->post?->user_id, get_class($comment->owner));

Enable DB::listen or ->dd() on the relevant query to see whether the relation was loaded or queried.

  • Extension points. The trait is small enough to override in your own base trait — reimplement owns to consult a custom resolver (role-based ownership, delegated ownership, team membership) and call the package method as a fallback. This keeps the public API your policies depend on stable regardless of the package's fate.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core