Typed DTOs
The fluent builder is the shortest way to book, but every write underneath takes a typed DTO. Pass them yourself from imports, API endpoints or queued jobs — through the facade, an injected AppointmentManager or an action (see DI and actions).
AppointmentData
create(), createRecurring() and CreateAppointmentAction take an AppointmentData — the fluent builder produces exactly this DTO under the hood:
use Carbon\CarbonImmutable;
use RoundlyConsulting\Appointments\DataTransferObjects\AppointmentData;
use RoundlyConsulting\Appointments\DataTransferObjects\ParticipantData;
use RoundlyConsulting\Appointments\Enums\ParticipantRole;
use RoundlyConsulting\Appointments\Facades\Appointments;
$appointment = Appointments::create(new AppointmentData(
name: 'Project kickoff',
startsAt: CarbonImmutable::parse('2026-07-01 17:30'),
durationMinutes: 90,
participants: [
new ParticipantData($host, ParticipantRole::Organiser),
new ParticipantData($guest),
],
));| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | — | Appointment title. |
startsAt | CarbonImmutable | — | Start; converted to UTC on save. |
durationMinutes | ?int | null | Duration; null uses default_duration_minutes. Below one throws InvalidScheduleException. |
timezone | ?string | null | Zone stored on the appointment; null stores appointments.timezone, else app.timezone. |
description | ?string | null | Description. |
meta | ?array | null | Arbitrary meta data. |
status | Status | Status::Pending | Initial status. |
participants | list of ParticipantData | [] | Participants to attach. |
recurrence | ?RecurrenceData | null | The rule createRecurring() falls back to when you pass none; create() ignores it. |
preventConflicts | bool | false | Double-booking guard for this call. |
location | ?string | null | Venue name. |
coordinates | ?Coordinates | null | Venue coordinates. |
contacts | list of ContactData | [] | Booking contacts to add. |
approval | ?AppointmentApprovalData | null | Approval request to open (approvers, rule, quorum, stages, workflow). |
recurrenceGroup | ?string | null | Links the occurrences of one series; set by createRecurring(). |
ParticipantData
One per participant: new ParticipantData(Model $participant, ?ParticipantRole $role = null, ?array $meta = null). Any Eloquent model can be the participant. AttachParticipantAction takes one too.
RecurrenceData
createRecurring() and ScheduleRecurringAppointmentAction take a RecurrenceData rule — its fields are listed in Recurring appointments. create() ignores AppointmentData::$recurrence; createRecurring() falls back to it when you pass no rule.
Validated on construction
Both DTOs refuse values that can’t describe a booking, before anything is written: an AppointmentData with a durationMinutes below one throws InvalidScheduleException, and a RecurrenceData with an interval or count below one, or a weekday outside 1–7, throws InvalidRecurrenceException.
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.