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 Membership Laravel Package

vimatech/laravel-membership

Vimatech's Laravel Membership package adds membership and subscription functionality to Laravel applications, helping developers manage member plans, subscriptions, and access control with a simple, integrated workflow for gated content and recurring member management.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require vimatech/laravel-membership
php artisan vendor:publish --tag="membership-migrations"
php artisan migrate

Publish the config too if you need to override table names or the default guard:

php artisan vendor:publish --tag="membership-config"

Where to look first: open the published migration. It tells you more about the package than the README does — you'll see the polymorphic member and membershipable (or equivalent) morph columns, the role column, the guard column, and the deleted_at column. That shape dictates everything you can do downstream: whether you can have one row per (member, target, role) or one row per pair.

Wire up the two sides of the relationship:

// app/Models/User.php
use Vimatech\Membership\Concerns\HasMemberships;

class User extends Authenticatable
{
    use HasMemberships;
}

// app/Models/Project.php  (also Team, Workspace — anything polymorphic)
use Vimatech\Membership\Concerns\HasMembers;

class Project extends Model
{
    use HasMembers;
}

Define a backed enum for your roles — this is the package's core opinion, so lean into it rather than fighting it:

enum ProjectRole: string
{
    case Viewer  = 'viewer';
    case Editor  = 'editor';
    case Manager = 'manager';
    case Owner   = 'owner';
}

First use case — the one to build before anything else:

$project->addMember($user, ProjectRole::Editor);

$user->hasRoleOn($project, ProjectRole::Editor);   // true
$user->hasRoleOn($project, ProjectRole::Owner);    // false

If that round-trips, the package is installed correctly. Then immediately test the "at least" form, because hierarchy is where most of your real authorization logic will live:

$user->hasAtLeastRoleOn($project, ProjectRole::Editor); // Editor, Manager, Owner all pass

Exact method names drift between pre-1.0 releases — confirm against the README shipped with the version you install, and pin the constraint in composer.json.

Implementation Patterns

Policy-driven authorization. This is the pattern that pays for itself. Keep controllers and Blade clean by putting the role check behind a policy:

class ProjectPolicy
{
    public function update(User $user, Project $project): bool
    {
        return $user->hasAtLeastRoleOn($project, ProjectRole::Editor);
    }

    public function delete(User $user, Project $project): bool
    {
        return $user->hasRoleOn($project, ProjectRole::Owner);
    }
}

Then $this->authorize('update', $project) and @can('update', $project) just work everywhere — including Gate::before super-admin bypasses you already have.

Treat the hierarchy as the single source of truth. Don't scatter in_array($role, ['manager','owner']) throughout the codebase. Define the ordering once (in the enum, or wherever the package reads it from — often an interface method) and always ask "at least". That way adding a new role between Manager and Owner is a one-line change instead of a grep-and-replace.

Multi-guard setups. If you have separate web and admin guards, or a User and a ServiceAccount that can both belong to the same project, use the guard column to keep them from colliding. Always pass the guard explicitly rather than relying on the default — otherwise a stray Auth::check() in a queued job will resolve against the wrong guard and silently return false.

Events for the audit trail. Membership changes are security-relevant; you want a timeline of "who granted what, when". Listen for the package's attach/detach/role-changed events and write them to an activity_log or membership_audit table. This is cheap to add on day one and nearly impossible to backfill later.

Event::listen(MembershipCreated::class, RecordMembershipAudit::class);
Event::listen(MembershipRoleChanged::class, RecordMembershipAudit::class);

Soft deletes for reversibility. Because the package soft-deletes memberships, "removing" someone keeps the row. Use that deliberately: show historical membership in an admin view, and when re-adding a previously-removed member, check whether you should restore the old row (preserving created_at) rather than inserting a duplicate. Watch for a unique index on the pivot in that case.

Query scoping. Two directions you'll need constantly:

$project->members;                          // everyone on the project
$project->membersWithRole(ProjectRole::Owner);
$user->memberships()->with('membershipable')->get();  // "my projects" dashboard

Eager-load the morph target (->with('membershipable')) on any list page — polymorphic relations will otherwise N+1 across every distinct model type, which is much worse than a normal N+1.

Seeding and onboarding. In seeders and in your "create project" action, add the creator as Owner explicitly. Don't rely on the package to infer it — an ownerless project is a support ticket waiting to happen.

Blade helpers. A single <x-role-badge :role="$membership->role" /> component plus @can covers almost all UI needs; resist building a parallel $user->isManager() API on top of the package.

Gotchas and Tips

Five stars and zero dependents means you are the QA team. Read the source before you build on a method — particularly around hierarchy resolution, guard handling, and soft-delete interaction. Budget half a day. Pin the version; pre-1.0 releases on a 2026-10-04 tag suggest the API can still move.

Register a morph map. Polymorphic packages and raw class names in the database are a bad combination — rename a model and every membership row orphans. In AppServiceProvider:

Relation::enforceMorphMap([
    'user'    => User::class,
    'team'    => Team::class,
    'project' => Project::class,
]);

Do this before you seed production data, not after.

Enum renames are migrations, not refactors. If roles are stored as the enum's string value, renaming a case or changing its backing value silently invalidates existing rows. Write a data migration (UPDATE memberships SET role = 'editor' WHERE role = 'author') in the same deploy, and consider a test that asserts every stored role value maps to a valid enum case.

Hierarchy cycles and gaps. If the ordering is expressible as data rather than code, it can be misconfigured. A "role X is above role Y" table with a bad edit produces authorization that is subtly wrong rather than loudly broken. Keep the hierarchy in code if you can; if it's configurable, add a boot-time assertion that the ordering is a valid total order.

Soft deletes break naive uniqueness. A unique(member_id, member_type, membershipable_id, membershipable_type) index plus soft deletes means re-inviting a removed user throws a constraint violation. Either restore the soft-deleted row, or include deleted_at in the index. Test the remove-then-re-add flow explicitly; it's the most common bug in this class of package.

Guard mismatches fail silently. hasRoleOn() returning false for a user you know is an owner is almost always a guard or morph-type mismatch, not a logic bug. Dump the raw membership row (withoutGlobalScopes(), including trashed) before you touch any application code.

Watch the query count. @can in a loop over 50 projects will hit the database 50 times unless you preload memberships for the current user and check in memory. For index pages, load $user->memberships once and resolve roles from the collection.

Extension points worth using: the published config (table names, guard), a custom pivot/membership model if you need extra columns (invited_by, expires_at, accepted_at), and the events. Avoid forking the package for behavior you can get from a listener — with this few users, your fork will never be rebased.

Test with RefreshDatabase and factories. Write one test per role transition (add, promote, demote, remove, re-add) against a real database. SQLite in-memory is fine, but if you use Postgres in production, run the suite against Postgres at least once — polymorphic + unique index behavior differs.

Have an exit plan. The package is a thin abstraction over a pivot table with a role column. If it stalls, you can inline the trait methods into your models in an afternoon. Keep your application code talking to your own policy classes and a small MembershipService, so the package is a detail behind a seam rather than something called from forty places.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route