NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

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),
    ],
));
FieldTypeDefaultMeaning
namestring—Appointment title.
startsAtCarbonImmutable—Start; converted to UTC on save.
durationMinutes?intnullDuration; null uses default_duration_minutes. Below one throws InvalidScheduleException.
timezone?stringnullZone stored on the appointment; null stores appointments.timezone, else app.timezone.
description?stringnullDescription.
meta?arraynullArbitrary meta data.
statusStatusStatus::PendingInitial status.
participantslist of ParticipantData[]Participants to attach.
recurrence?RecurrenceDatanullThe rule createRecurring() falls back to when you pass none; create() ignores it.
preventConflictsboolfalseDouble-booking guard for this call.
location?stringnullVenue name.
coordinates?CoordinatesnullVenue coordinates.
contactslist of ContactData[]Booking contacts to add.
approval?AppointmentApprovalDatanullApproval request to open (approvers, rule, quorum, stages, workflow).
recurrenceGroup?stringnullLinks 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 crypto

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