Installation
Require the package, run its installer, then migrate. The service provider is auto-discovered together with the providers of the Roundly packages it builds on. The facade has no global alias — import RoundlyConsulting\Events\Facades\Events:
composer require roundly-consulting/events-for-laravel
php artisan events:install
php artisan migrateevents:install refuses while any configured table already exists — events is a common host table name (analytics, event sourcing) — and names the events.tables.* key to change. Otherwise it publishes events-config and the migrations lifecycle-migrations, tags-migrations, media-migrations and events-migrations (plus sluggable-migrations with slug history on), keeps files you already published, and prints the key types side by side with the next steps.
1. Settle the key types before migrating
The migrations read two key types to pick their column types. With bigint user ids the defaults fit. With UUID or ULID keys, set them before php artisan migrate:
EVENTS_PRIMARY_KEY_TYPE=uuid # events and every column pointing at one (= lifecycle.key_type, tags.key_type)
EVENTS_KEY_TYPE=uuid # organizers, attendees, co-hosts, invitees, actors (= your users)
LIFECYCLE_KEY_TYPE=uuid
LIFECYCLE_ACTOR_KEY_TYPE=uuid
TAGS_KEY_TYPE=uuid| Config | Env | Governs | Must equal |
|---|---|---|---|
events.primary_key_type | EVENTS_PRIMARY_KEY_TYPE | events.id and every column pointing at an event (lifecycle subjects, tag taggables, media owners, slug history) | lifecycle.key_type, tags.key_type (+ sluggable.key_type with slug history) |
events.key_type | EVENTS_KEY_TYPE | every column pointing at your models: organizer, attendee, co-host, invitee, inviter, venue owner, added_by, checked_in_by | your users’ key type and lifecycle.actor_key_type |
With events.tags.scope = organizer the organizer is a tag scope, so events.key_type must equal tags.key_type too. A model whose key does not fit is refused before any SQL, naming events.key_type — PostgreSQL would otherwise answer with a raw “invalid input syntax for type bigint” — and events:doctor reports every mismatch.
2. Rename the tables if you need to
Every table name is configurable. Rename them before installing when your app already has an events table:
// config/events.php — set before events:install and php artisan migrate
'tables' => [
'events' => 'meetup_events', // your app already has an events table
'venues' => 'event_venues',
'tiers' => 'event_admission_tiers',
'rsvps' => 'event_rsvps',
'hosts' => 'event_hosts',
'invitations' => 'event_invitations',
'reminder_deliveries' => 'event_reminder_deliveries',
],3. Add the traits to your models
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use RoundlyConsulting\Events\Concerns\AttendsEvents;
use RoundlyConsulting\Events\Concerns\OrganizesEvents;
use RoundlyConsulting\Events\Contracts\Organizer;
class User extends Authenticatable implements Organizer
{
use AttendsEvents; // $user->rsvp($event)->going(), isGoingTo(), eventFeed(), seats freed on deletion
use Notifiable; // notices
use OrganizesEvents; // the Organizer contract's defaults
}AttendsEvents lets the model RSVP and read its events, OrganizesEvents gives the Organizer contract’s defaults and Notifiable receives the notices. Then list the models the API may reference, by alias — teams as organizers are covered on Organizers, co-hosts and invitations:
// config/events.php — the aliases the JSON API uses, never class names
'organizers' => [
'user' => App\Models\User::class,
'team' => App\Models\Team::class, // list every organizer class, teams included
],
'attendees' => [
'user' => App\Models\User::class,
],4. Schedule the sweeps
// routes/console.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('events:sweep')->everyMinute(); // held seats expire, waitlists move up, reminders go out
Schedule::command('lifecycle:sweep')->everyMinute(); // finished events complete at their endNothing is put on the scheduler for you. Without events:sweep held seats expire only on the next write to their event and no reminder goes out; without lifecycle:sweep finished events never complete by themselves.
5. Check the installation
php artisan events:doctor
php artisan events:doctor --counts # also compare every event's counters with a recount
php artisan events:doctor --json # [{severity, check, message}]Run the doctor after installing and after every configuration change — the list of checks is on Artisan commands.
Without the installer
php artisan vendor:publish --tag=events-config --tag=lifecycle-migrations \
--tag=tags-migrations --tag=media-migrations --tag=events-migrationsThe migrations are publish-only — the package never loads them — and timestamped on publish.
| Tag | Lands in |
|---|---|
events-config | config/events.php |
events-migrations | Seven migrations, …_0001_create_event_venues_table.php to …_0007_create_event_reminder_deliveries_table.php — one second apart; republishing lands on the same files. |
events-translations | lang/vendor/events/{en,sk}/*.php |
events-views | resources/views/vendor/events/json-ld.blade.php |
events-routes | routes/events.php — once there, it loads instead of the package’s file. |
php artisan vendor:publish --tag=events-translations # lang/vendor/events/{en,sk}/*.php
php artisan vendor:publish --tag=events-views # resources/views/vendor/events/json-ld.blade.php
php artisan vendor:publish --tag=events-routes # routes/events.php — loads instead of the package'sLocales and translations
Titles, descriptions, ticket type and venue names and event slugs are per locale through translatable-for-laravel: pass a locale map (['en' => 'Jazz Night', 'sk' => 'Jazzový večer']) or a string in the current locale; reads follow the current locale, then the fallback chain. The app’s locales must be in translatable.locales (default en, sk) — tagging by name throws invalid_tag otherwise. The package’s own strings ship in English and Slovak with identical keys, in the events:: namespace:
| File | Holds |
|---|---|
enums.php | Enum labels (label()). |
messages.php | Every exception and denial message — the API’s message. |
notifications.php | Subjects, greetings, bodies and buttons of the notices. |
validation.php | The API’s field names in validation messages. |
tags.php | The labels of the two tag types. |
feeds.php | Calendar texts — the join line, the personal feed’s name. |
Publish them with --tag=events-translations to change a line or add a language.
php artisan about
php artisan about --only=eventsThe Events section shows the shape of the installation — never a secret:
| Row | Example |
|---|---|
| Key type | bigint (events), bigint (people) |
| Lifecycle | EventLifecycle |
| Routes | ON / OFF |
| Notifications | ON (mail, database) / OFF |
| Reminders | P1D, PT2H / NONE |
| Payments | NoPaymentCollector |
| Geocoding | ON / OFF |
| Slug history | ON / OFF |
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.