Events for Laravel works on its own. These recipes plug in other Roundly packages, each one optional.
Guests without accounts
Any model can attend. Give guests their own model — with the same key type as your users — make it notifiable by mail and list it as an attendee alias:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use RoundlyConsulting\Events\Concerns\AttendsEvents;
final class Guest extends Model
{
use AttendsEvents;
use Notifiable;
protected $fillable = ['name', 'email'];
public function routeNotificationForMail(): string
{
return $this->email;
}
}
// config/events.php
'attendees' => ['user' => App\Models\User::class, 'guest' => App\Models\Guest::class],
$guest = Guest::firstOrCreate(['email' => $request->string('email')], ['name' => $request->string('name')]);
Events::for($event)->rsvp($guest)->going();A guest is never an Authenticatable, so it answers invitations by token alone; the invitee check applies to user accounts only. A model without notification routes still attends — it just receives no notices.
Team organizers
With Teams for Laravel, a team organizes events and every member manages them — membership in PHP and in SQL, so the policy and managedBy() agree:
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Events\Concerns\OrganizesEvents;
use RoundlyConsulting\Events\Contracts\Organizer;
use RoundlyConsulting\Teams\Models\Team as BaseTeam;
final class Team extends BaseTeam implements Organizer
{
use OrganizesEvents;
public function allowsEventManagementBy(Model $user): bool
{
return $this->members()->where('member_type', $user->getMorphClass())->where('member_id', $user->getKey())->exists();
}
public function scopeManagingEventsFor(Builder $query, Model $user): void
{
$query->whereHas('members', fn (Builder $members) => $members
->where('member_type', $user->getMorphClass())->where('member_id', $user->getKey()));
}
}
// config/teams.php: 'models' => ['team' => App\Models\Team::class]
// config/events.php: 'organizers' => ['user' => User::class, 'team' => App\Models\Team::class]Narrow it to a role (hasTeamRole()) by adding the condition to both methods.
Custom fields
Swap the event model and give it typed custom fields from Attributes for Laravel:
use RoundlyConsulting\Attributes\Contracts\HasAttributes as HasAttributesContract;
use RoundlyConsulting\Attributes\Facades\Attributes;
use RoundlyConsulting\Attributes\Traits\HasAttributes;
final class Event extends \RoundlyConsulting\Events\Models\Event implements HasAttributesContract
{
use HasAttributes;
}
// config/events.php: 'models' => ['event' => App\Models\Event::class]
// define the field once (attributes-for-laravel's Attributes::define()), then:
Attributes::for($event)->set('dress_code', 'smart casual');Event discussion
A discussion under each event with Comments for Laravel:
use RoundlyConsulting\Comments\Facades\Comments;
use RoundlyConsulting\Comments\Traits\HasComments;
final class Event extends \RoundlyConsulting\Events\Models\Event
{
use HasComments;
}
// config/events.php: 'models' => ['event' => App\Models\Event::class]
Comments::on($event)->as($user)->body('Is there parking nearby?')->post();Gate who may comment with the event’s own abilities — view, or rsvp for attendees only.
Reviews after the event
Reviews from attendees after the event, with Reviews for Laravel:
use Illuminate\Support\Facades\Event as EventBus; // your Event model shadows Laravel's facade name
use RoundlyConsulting\Events\Enums\EventPhase;
use RoundlyConsulting\Events\Events\EventCompleted;
use RoundlyConsulting\Events\Facades\Events;
use RoundlyConsulting\Reviews\Concerns\HasReviews;
use RoundlyConsulting\Reviews\Facades\Reviews;
final class Event extends \RoundlyConsulting\Events\Models\Event
{
use HasReviews;
}
// config/events.php: 'models' => ['event' => App\Models\Event::class]
EventBus::listen(EventCompleted::class, function (EventCompleted $completed): void {
$attendees = Events::for($completed->event)->attendees()->checkedIn(); // invite them to review
});
if ($user->isGoingTo($event) && $event->phase === EventPhase::Completed) {
Reviews::for($event)->by($user)->rating(5)->content('Great night')->verified()->create();
}An overbooking ratio for free events
No-shows are common at free events. Keep the real room size in your own column and give the event a larger capacity — the waitlist, counters and check-in work unchanged:
$room = 100;
Events::make('Free meetup')->organizedBy($user)->capacity((int) floor($room * 1.2))->tier('Free entry')->publish();Check-in tells you who actually came (attendees()->checkedIn()); adjust the ratio per series.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.