---
title: "Participants — Appointments for Laravel | Roundly"
description: "Attach any Eloquent model as a participant with a role and meta, list a model’s appointments with HasAppointments, and add or remove people later."
url: https://roundly-consulting.com/open-source/docs/appointments-for-laravel/participants
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Appointments for Laravel](https://roundly-consulting.com/open-source/docs/appointments-for-laravel.md)

# Participants

Participants aren’t limited to users — any Eloquent model can take part through the polymorphic participants table: a customer, a staff member, a meeting room, a vehicle. Add HasAppointments to each model that should see its appointments:

```php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
use RoundlyConsulting\Appointments\Concerns\HasAppointments;

class User extends Authenticatable
{
    use HasAppointments;
}

class Room extends Model
{
    use HasAppointments;
}

$user->appointments()->upcoming()->ordered()->get();   // appointments the user takes part in
$room->appointments()->between($monday, $friday)->get();
$user->appointmentParticipations;                       // the participant rows (MorphMany)
```

appointments() returns a query builder rather than a relation, so chain any scope before get(). appointmentParticipations() is the MorphMany of participant rows.

## Roles and per-participant meta

Give each participant an optional role and any meta you need — a host flag, party size, a seat number:

```php
use RoundlyConsulting\Appointments\Enums\ParticipantRole;
use RoundlyConsulting\Appointments\Facades\Appointments;

Appointments::schedule('Property viewing')
    ->startingAt('2026-07-03 14:00')
    ->lasting(30)
    ->withParticipant($agent, ParticipantRole::Organiser)
    ->withParticipant($buyer, ParticipantRole::Attendee, ['party_size' => 2])
    ->withParticipant($partner, ParticipantRole::Optional)
    ->withParticipant($flat)            // any Eloquent model — role stays null
    ->create();
```

| Case | Value | Notes |
| --- | --- | --- |
| `ParticipantRole::Organiser` | organiser | The person running the appointment. |
| `ParticipantRole::Attendee` | attendee | Returned by ParticipantRole::default(). |
| `ParticipantRole::Optional` | optional | Invited, but not required. |

## Reading participants

```php
$appointment->participants;                    // HasMany<Participant>, loaded after create()

$participant = $appointment->participants->first();
$participant->participant;                     // MorphTo — the underlying model (e.g. a User)
$participant->appointment;                     // BelongsTo<Appointment>
$participant->role;                            // ?ParticipantRole
$participant->meta?->get('party_size');        // meta is cast to a Collection
```

## Adding and removing participants later

Appointments::for($appointment)->participants() manages who takes part in one booking:

```php
use RoundlyConsulting\Appointments\Enums\ParticipantRole;
use RoundlyConsulting\Appointments\Facades\Appointments;

$participants = Appointments::for($appointment)->participants();

$row = $participants->add($interpreter, ParticipantRole::Optional, meta: ['language' => 'sk']);
$participants->add($room, preventConflicts: true);   // refuse if the room is booked then

$participants->has($interpreter);    // true
$participants->all();                // the participant rows (role, meta), models eager-loaded
$participants->remove($interpreter); // by model…
$participants->remove($row);         // …or by one of this appointment's participant rows
```

- Adding a model that already takes part throws DuplicateParticipantException.
- With preventConflicts: true (or prevent\_conflicts in config), adding a model that is booked elsewhere in the appointment’s window throws SchedulingConflictException.
- Removing a model that doesn’t take part — or a participant row that belongs to another appointment — throws ParticipantNotFoundException. Nothing is deleted.
- Removal soft-deletes the row and fires ParticipantDeleted. Adding the same model again restores that row with the new role and meta (ParticipantUpdated); a first-time add fires ParticipantCreated.

Both models use soft deletes: deleting a participant or an appointment keeps the row with a deleted\_at timestamp.

## 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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/appointments-for-laravel.md)
