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 Invite Codes Laravel Package

mateusjunges/laravel-invite-codes

Create, manage, and track invite codes in Laravel. Generate unique codes, set expiration dates and usage limits, associate codes with users or models, and record redemptions—ideal for beta signups, referrals, or gated registration.

View on GitHub
Deep Wiki
Context7

Getting Started

mateusjunges/laravel-invite-codes is a small, focused package that does one job: generate, hand out, expire, and redeem invite codes for your application's users (or any Eloquent model). It ships with migrations, a config file, a trait, a facade, a route middleware, and events — no UI, no queue jobs, no opinions about how you gate signup.

Install and scaffold (3 commands):

composer require mateusjunges/laravel-invite-codes
php artisan vendor:publish --tag=invite-codes-migrations
php artisan vendor:publish --tag=invite-codes-config
php artisan migrate

First use case — invite-only registration. The 15-minute path:

  1. Add the trait to the model that owns the codes (usually User):
use Mateusjunges\InviteCodes\Traits\HasInviteCodes;

class User extends Authenticatable
{
    use HasInviteCodes;
}
  1. Generate a code for an existing user:
$code = $user->createInviteCode();      // single
$codes = $user->createInviteCodes(5);   // batch, returns a collection
  1. Redeem it during registration and attach the new user:
if (! $user->redeemInviteCode($request->input('invite_code'))) {
    return back()->withErrors(['invite_code' => 'Invalid or expired code.']);
}

Where to look first: read config/invite-codes.php end-to-end before writing any code. It controls code length, character set, prefix/suffix, default max uses, default expiry, and the middleware alias. Then skim the published migrations — the column names (code, max_uses, uses, expires_at, plus the owner/invitee foreign keys) tell you more about the package's mental model than the README does.

On v2.4.0: this release adds Laravel 13 support and nothing else — no API changes, no deprecations, no config keys added or removed. If you are already on ^2.3, upgrading is a no-op for your code. If you are starting a Laravel 13 project, this is the first version that will install cleanly; pin "mateusjunges/laravel-invite-codes": "^2.4" so a future composer update on a fresh checkout can't resolve down to a pre-L13 release on a clean install.

Implementation Patterns

Two entry points, same underlying model. You can drive everything through the HasInviteCodes trait (fluent, scoped to a user) or through the InviteCodes facade (global, useful in jobs, seeders, and admin panels):

use Mateusjunges\InviteCodes\Facades\InviteCodes;

// Trait style: code is owned by $user
$user->createInviteCode();

// Facade style: chainable builder
InviteCodes::create()->for($user)->withMaxUses(3)->save();
InviteCodes::create()->expiresIn('7 days')->save();
InviteCodes::create()->expiresAt(now()->addMonth())->save();

// Lookups and cleanup
InviteCodes::find($code);
InviteCodes::getAll();
InviteCodes::delete($code);

Prefer the builder for anything with policy attached (withMaxUses, expiresAt) and the trait for the "give this user a code" button in an admin panel. Mixing the two is fine — they produce the same rows.

Multi-use team invites. max_uses is the feature that separates this package from a random string column:

InviteCodes::create()
    ->for($teamOwner)
    ->withMaxUses(10)
    ->expiresIn('30 days')
    ->save();

Every successful redeemInviteCode() increments uses and stamps the invitee. Render max_uses - uses in your UI as "seats remaining" rather than exposing the raw counters.

Middleware as a cheap gate. The package registers a route middleware alias (configurable in config/invite-codes.php) that pulls the code out of the request and rejects invalid ones before your controller runs:

Route::post('/register', RegisterController::class)
    ->middleware(['invite-code']);

This is the fastest way to bolt invite-gating onto an existing registration flow without touching the controller. Use it when the only requirement is "no valid code, no entry." Drop it and validate manually when you need custom error messages, per-plan limits, or to record which code was used for attribution.

Wrap creation in a transaction when it's part of onboarding. Inviting a user typically means creating the invite code and sending an invite record/mail. Do the DB work in a transaction and dispatch the notification after commit, otherwise a rolled-back invite can leave a live code circulating:

DB::transaction(function () use ($user) {
    $code = $user->createInviteCode();
    Invite::create(['code' => $code->code, 'email' => $email]);
});

Hook the events for side effects. Code creation and redemption both fire events. Listen to them for audit logging, analytics, or "your invite was used" notifications to the inviter — cleaner than decorating controllers:

Event::listen(InviteCodeRedeemed::class, function ($event) {
    // notify inviter, increment a funnel metric, etc.
});

Seeders and local dev. InviteCodes::create()->for(User::factory()->create())->save() in a seeder gives QA a known code. Better: generate a fixed, readable code (short code_length, digits only) in local/testing env via a config override so test fixtures aren't hunting random strings.

Testing. Use RefreshDatabase plus the facade to create codes inline. Assert both the happy path and the three failure modes: wrong code, expired (expires_at in the past), and exhausted (uses >= max_uses). Those three are where this package's bugs will live in your app.

Gotchas and Tips

  • Code collisions are your problem to size for. The code column is unique; with the default character set, an 8-character code has plenty of entropy, but if you shorten code_length to 4 for "nicer" invite codes, expect intermittent uniqueness failures under load. Bump length, not retry loops, unless you have measured a problem.
  • redeemInviteCode() failing is a normal return, not an exception. It returns falsy for invalid/expired/exhausted codes. Always branch on the result — a bare $user->redeemInviteCode($code); that ignores the return value is the single most common integration bug with this package.
  • expires_at = null means never expires. If you want default expiry, set it in config; if you want no codes to be permanent, enforce that at your creation call sites or in a model observer. Nothing stops a caller from omitting expiresIn().
  • Publish migrations before you migrate, and edit them if your schema differs. The migration assumes a users table for the owner/invitee foreign keys. Non-standard user tables, UUID/ULID keys, or a separate teams owner model require editing the published migration — once it has run in production, this becomes a manual migration.
  • Config is cached. Changing code_length, prefix/suffix, or the middleware alias in a cached environment does nothing until php artisan config:cache runs again. This burns people in CI where config:cache is part of the build step.
  • Changing code_characters or code_length does not retroactively affect existing codes. Old codes keep validating; only new ones use the new format. Fine in practice, confusing during debugging.
  • The middleware expects a specific request key (invite_code by default). If your form field is named code, you'll get a rejection that looks like a validation logic bug. Confirm the key before blaming the package.
  • Deleting a user does not necessarily clean up their codes — check the FK onDelete behaviour in the published migration and add a cascade or a pruning job if orphaned codes are a concern.
  • Laravel 13 support landed in v2.4.0 only. There are no other changes in this release, so if you are on Laravel 11/12 nothing in your app needs to move. If you are on Laravel 13, ^2.4 is your floor — older tags will fail dependency resolution.
  • Extension points are shallow, by design. If you need custom code formats (e.g. TEAM-XXXX-XXXX), the cleanest route is a wrapper service that uses the facade with an explicit prefix/suffix, not forking the package. For custom validation semantics (domain restrictions, one-code-per-email), keep that logic in your own FormRequest and use the package purely as storage.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
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