A Laravel package for managing user invitations with polymorphic relationships, token-based access, scheduled reminders, and event-driven notifications.
- Polymorphic invitations - Invite users to any model (teams, organizations, projects)
- Bulk invitations - Invite multiple users at once with partial failure handling
- Token-based secure links - Shareable invitation URLs with secure tokens
- Status tracking - Pending, accepted, declined, expired, and cancelled states
- Automatic reminders - Scheduled reminder emails for pending invitations
- Event-driven - Events fired for all invitation lifecycle changes
- Translatable notifications - All notification messages customizable via language files
- Structured exceptions - Error codes and resolution hints for easy debugging
- Requirements
- Installation
- Quick Start
- Full Documentation
- AI Coding Assistant Skill
- Testing
- Contributing
- License
- PHP 8.2+
- Laravel 11/12/13
composer require offload-project/laravel-invite-only
php artisan vendor:publish --tag="invite-only-config"
php artisan vendor:publish --tag="invite-only-migrations"
php artisan migrate// Team.php (or any model that can have invitations)
use OffloadProject\InviteOnly\Traits\HasInvitations;
class Team extends Model
{
use HasInvitations;
}// User.php
use OffloadProject\InviteOnly\Traits\CanBeInvited;
class User extends Authenticatable
{
use CanBeInvited;
}If a model both sends and receives invitations (e.g. user-to-user friend
invitations), apply both traits. The two traits each define an
acceptedInvitations() method, so use PHP's trait conflict resolution:
use OffloadProject\InviteOnly\Traits\CanBeInvited;
use OffloadProject\InviteOnly\Traits\HasInvitations;
class User extends Authenticatable
{
use HasInvitations, CanBeInvited {
CanBeInvited::acceptedInvitations insteadof HasInvitations;
HasInvitations::acceptedInvitations as acceptedInvitationsToModel;
}
}In v3.0 the HasInvitations::acceptedInvitations() helper will be removed in
favour of getAcceptedInvitations(), eliminating the conflict — at which point
the insteadof/as clauses can be dropped.
// Single invitation
$team->invite('user@example.com', [
'role' => 'member',
'invited_by' => auth()->user(),
]);
// Bulk invitations
$result = $team->inviteMany(
['one@example.com', 'two@example.com', 'three@example.com'],
['role' => 'member', 'invited_by' => auth()->user()]
);
$result->successful; // Collection of created invitations
$result->failed; // Collection of failures with reasonsuse OffloadProject\InviteOnly\Events\InvitationAccepted;
Event::listen(InvitationAccepted::class, function ($event) {
$team = $event->invitation->invitable;
$user = $event->user;
$role = $event->invitation->role;
$team->users()->attach($user->id, ['role' => $role]);
});// routes/console.php
Schedule::command('invite-only:send-reminders --mark-expired')->daily();- Getting Started - Step-by-step tutorial
- API Reference - All methods, events, and configuration
- Concepts - Lifecycle, architecture, and design decisions
This package ships a Laravel Boost skill so coding assistants (Claude Code, Cursor, etc.) follow the package's conventions when generating code. Install it in your app with:
php artisan boost:add-skill offload-project/laravel-invite-onlyThe skill source lives at skills/SKILL.md.
composer testContributions are welcome! Please see the documents below before getting started.
- Contributing Guide — setup, workflow, commit conventions, and PR process
- Code of Conduct — expectations for participation in this project
- Security Policy — how to report a vulnerability privately
The MIT License (MIT). Please see License File for more information.