Manage multiple waitlists in Laravel - perfect for beta programs, product launches, and feature access control
offload-project/laravel-waitlist is a Laravel package for manage multiple waitlists in laravel - perfect for beta programs, product launches, and feature access control.
It currently has 55 GitHub stars and 2.344 downloads on Packagist (latest version v3.2.0).
Install it with composer require offload-project/laravel-waitlist.
Discover more Laravel packages by offload-project
or browse all Laravel packages to compare alternatives.
Last updated
A simple and flexible waitlist package for Laravel applications. Manage multiple waitlists with ease — perfect for beta programs, product launches, feature access, and more.
This package provides the core functionality without imposing any UI or API structure, giving you complete freedom to implement your own controllers, views, and API endpoints.
offload-project/laravel-invite-only for token-based flowscomposer require offload-project/laravel-waitlist
php artisan vendor:publish --tag="waitlist-config"
php artisan vendor:publish --tag="waitlist-migrations"
php artisan migrate
If you only need one waitlist, just start using it — a default waitlist is created automatically:
use OffloadProject\Waitlist\Facades\Waitlist;
// Add users to the default waitlist
$entry = Waitlist::add('John Doe', '[email protected]');
// Invite a user (sends notification automatically)
Waitlist::invite($entry);
// Get statistics
$total = Waitlist::count();
$pending = Waitlist::countPending();
Create and manage multiple waitlists for different purposes:
use OffloadProject\Waitlist\Facades\Waitlist;
// Create separate waitlists
$beta = Waitlist::create('Beta Program', 'beta', 'Early access to new features');
$launch = Waitlist::create('Product Launch', 'launch', 'Get notified when we launch');
$vip = Waitlist::create('VIP Access', 'vip', 'Premium tier waitlist');
// Add users to specific waitlists
Waitlist::for('beta')->add('John Doe', '[email protected]');
Waitlist::for('launch')->add('Jane Smith', '[email protected]');
Waitlist::for('vip')->add('Bob Wilson', '[email protected]');
// Same person can join multiple waitlists
Waitlist::for('beta')->add('Alice Johnson', '[email protected]');
Waitlist::for('launch')->add('Alice Johnson', '[email protected]');
// Get entries for a specific waitlist
$betaEntries = Waitlist::for('beta')->getPending();
$launchCount = Waitlist::for('launch')->count();
// Invite users from a specific waitlist
$entry = Waitlist::for('beta')->getByEmail('[email protected]');
Waitlist::invite($entry);
use OffloadProject\Waitlist\Facades\Waitlist;
// Create waitlists
$beta = Waitlist::create('Beta Program', 'beta', 'Description');
$waitlist = Waitlist::find('beta'); // Find by slug
// Add users
$entry = Waitlist::for('beta')->add('John Doe', '[email protected]');
// Add with metadata
$entry = Waitlist::for('launch')->add('Jane Doe', '[email protected]', [
'referral_source' => 'twitter',
'interest' => 'premium',
'company' => 'Acme Inc',
]);
// Invite and reject
Waitlist::invite($entry); // By model
Waitlist::invite($entryId); // By ID
Waitlist::reject($entry);
Waitlist::reject($entryId);
// Pass options through to the underlying invitation
$entry = Waitlist::for('beta')->getByEmail('[email protected]');
Waitlist::invite($entry, [
'invited_by' => $admin, // Model or int; falls back to auth()->user()
'role' => 'beta-tester',
'metadata' => ['cohort' => 'wave-3'],
'expires_at' => now()->addDays(14),
]);
// Query entries
$pending = Waitlist::for('beta')->getPending();
$invited = Waitlist::for('beta')->getInvited();
$all = Waitlist::for('beta')->getAll();
$entry = Waitlist::for('beta')->getByEmail('[email protected]');
// Check existence
if (Waitlist::for('beta')->exists('[email protected]')) {
// User is on the beta waitlist
}
// Get statistics
$total = Waitlist::for('beta')->count();
$pending = Waitlist::for('beta')->countPending();
$invited = Waitlist::for('beta')->countInvited();
// Manage waitlist status
$beta->activate();
$beta->deactivate();
$beta->isActive(); // true/false
You can also work directly with the WaitlistEntry model:
use OffloadProject\Waitlist\Models\WaitlistEntry;
// Create an entry
$entry = WaitlistEntry::create([
'name' => 'Jane Doe',
'email' => '[email protected]',
'metadata' => ['source' => 'landing-page'],
]);
// Check status
if ($entry->isPending()) {
// Entry is pending
}
if ($entry->isInvited()) {
// Entry has been invited
}
if ($entry->isRejected()) {
// Entry was rejected
}
// Update status
$entry->markAsInvited();
$entry->markAsRejected();
// Query entries
$pending = WaitlistEntry::where('status', 'pending')->get();
$recent = WaitlistEntry::latest()->take(10)->get();
Since this package doesn't include controllers, you can create your own to fit your needs:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use OffloadProject\Waitlist\Facades\Waitlist;
class WaitlistController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:waitlist_entries,email',
]);
$entry = Waitlist::add(
$validated['name'],
$validated['email']
);
return response()->json([
'message' => 'Successfully added to waitlist!',
'data' => $entry,
], 201);
}
public function stats()
{
return response()->json([
'total' => Waitlist::count(),
'pending' => Waitlist::countPending(),
'invited' => Waitlist::countInvited(),
]);
}
}
Example Livewire component:
namespace App\Livewire;
use Livewire\Component;
use OffloadProject\Waitlist\Facades\Waitlist;
class WaitlistForm extends Component
{
public $name = '';
public $email = '';
public $success = false;
public function submit()
{
$this->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:waitlist_entries,email',
]);
Waitlist::add($this->name, $this->email);
$this->success = true;
$this->reset(['name', 'email']);
}
public function render()
{
return view('livewire.waitlist-form');
}
}
Publish the config file and change the notification class:
// config/waitlist.php
'notification' => \App\Notifications\CustomWaitlistInvited::class,
Create your custom notification:
namespace App\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use OffloadProject\Waitlist\Models\WaitlistEntry;
class CustomWaitlistInvited extends Notification
{
public function __construct(public WaitlistEntry $entry) {}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject('Welcome to Our Platform!')
->greeting("Hi {$this->entry->name}!")
->line('Great news! Your wait is over.')
->action('Get Started', url('/register'))
->line('We can\'t wait to see what you build!');
}
}
To send invitations manually instead of automatically, disable auto-send:
// config/waitlist.php
'auto_send_invitation' => false,
use OffloadProject\Waitlist\Notifications\WaitlistInvited;
$entry = Waitlist::getByEmail('[email protected]');
$entry->notify(new WaitlistInvited($entry));
Optionally require users to verify their email before they can be invited:
// config/waitlist.php
'verification' => [
'enabled' => true, // Enable email verification
'require_before_invite' => true, // Block invites until verified
],
Or via environment variables:
WAITLIST_VERIFICATION_ENABLED=true
WAITLIST_REQUIRE_VERIFICATION=true
When verification is enabled:
use OffloadProject\Waitlist\Facades\Waitlist;
// Adding an entry automatically sends a verification email
$entry = Waitlist::add('John Doe', '[email protected]');
// Check verification status
$entry->isVerified(); // false initially
$entry->isPendingVerification(); // true after verification email sent
// Manually send/resend verification email
Waitlist::sendVerification($entry);
// Verify programmatically (normally handled by the verification route)
Waitlist::verify($token);
// Attempting to invite an unverified entry throws an exception
// when require_before_invite is true
try {
Waitlist::invite($entry);
} catch (\OffloadProject\Waitlist\Exceptions\UnverifiedEntryException $e) {
// Handle unverified entry
}
The package provides a verification route at /waitlist/verify/{token} by default. Configure the routes in your config:
// config/waitlist.php
'routes' => [
'enabled' => true, // Set to false to define your own routes
'prefix' => 'waitlist', // URL prefix
'middleware' => ['web'], // Middleware to apply
],
To customize the verification notification:
// config/waitlist.php
'verification' => [
'enabled' => true,
'require_before_invite' => true,
'notification' => \App\Notifications\CustomVerifyEmail::class,
],
namespace App\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use OffloadProject\Waitlist\Models\WaitlistEntry;
class CustomVerifyEmail extends Notification
{
public function __construct(public WaitlistEntry $entry) {}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
$url = route('waitlist.verify', ['token' => $this->entry->verification_token]);
return (new MailMessage)
->subject('Confirm your spot on the waitlist')
->greeting("Hi {$this->entry->name}!")
->line('Please verify your email to secure your place.')
->action('Verify Email', $url);
}
}
return [
// The model class used for waitlist entries
'model' => \OffloadProject\Waitlist\Models\WaitlistEntry::class,
// Database table name
'table' => 'waitlist_entries',
// Auto-send invitation notifications
'auto_send_invitation' => true,
// Notification class for invitations
'notification' => \OffloadProject\Waitlist\Notifications\WaitlistInvited::class,
// Email verification settings
'verification' => [
'enabled' => false, // Enable/disable email verification
'require_before_invite' => true, // Require verification before inviting
'notification' => \OffloadProject\Waitlist\Notifications\VerifyWaitlistEmail::class,
],
// Route configuration
'routes' => [
'enabled' => true, // Enable package routes
'prefix' => 'waitlist', // URL prefix
'middleware' => ['web'], // Middleware
],
];
waitlists tableid — Primary keyname — Waitlist nameslug — Unique identifier for referencing the waitlistdescription — Optional descriptionis_active — Whether the waitlist is active (default: true)settings — JSON field for custom settingscreated_at / updated_at — Laravel timestampsIndexed fields: slug, is_active
waitlist_entries tableid — Primary keywaitlist_id — Foreign key to the waitlist (nullable for default waitlist)name — User's nameemail — User's email (unique per waitlist)status — Status: pending, invited, or rejectedinvited_at — Timestamp when invitedmetadata — JSON field for custom dataverification_token — Token for email verification (nullable)verified_at — Timestamp when email was verified (nullable)invitation_id — Optional FK to offload-project/laravel-invite-only invitation (nullable)created_at / updated_at — Laravel timestampsIndexed fields: status, created_at, verification_token
Unique constraint: ['waitlist_id', 'email'] (same email can join multiple waitlists)
// Managing waitlists
Waitlist::create(string $name, string $slug, ?string $description = null, bool $isActive = true): Waitlist
Waitlist::find(string $slug): ?Waitlist
Waitlist::for(string|int|Waitlist $waitlist): self // Set waitlist context
Waitlist::getDefault(): Waitlist
// Adding entries (uses current waitlist context or default)
Waitlist::add(string $name, string $email, array $metadata = []): WaitlistEntry
// Managing status
Waitlist::invite(int|WaitlistEntry $entry, array $options = []): WaitlistEntry
Waitlist::reject(int|WaitlistEntry $entry): WaitlistEntry
// Email verification
Waitlist::sendVerification(int|WaitlistEntry $entry): WaitlistEntry
Waitlist::verify(string $token): ?WaitlistEntry
// Retrieving entries (uses current waitlist context or default)
Waitlist::getPending(): Collection
Waitlist::getInvited(): Collection
Waitlist::getAll(): Collection
Waitlist::getByEmail(string $email): ?WaitlistEntry
// Checking existence
Waitlist::exists(string $email): bool
// Counting
Waitlist::count(): int
Waitlist::countPending(): int
Waitlist::countInvited(): int
// Relationships
$waitlist->entries(): HasMany
// Status checks
$waitlist->isActive(): bool
// Status updates
$waitlist->activate(): self
$waitlist->deactivate(): self
// Status checks
$entry->isPending(): bool
$entry->isInvited(): bool
$entry->isRejected(): bool
// Verification checks
$entry->isVerified(): bool
$entry->isPendingVerification(): bool
// Status updates
$entry->markAsInvited(): self
$entry->markAsRejected(): self
$entry->markAsVerified(): self
$entry->generateVerificationToken(): self
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-waitlist
The skill source lives at skills/SKILL.md.
composer test
Contributions are welcome! Please see the documents below before getting started.
The MIT License (MIT). Please see License File for more information.