---
title: "Typed DTOs — Appointments for Laravel | Roundly"
description: "AppointmentData, ParticipantData and RecurrenceData — the typed DTOs behind create(), createRecurring(), the fluent builder and the actions."
url: https://roundly-consulting.com/open-source/docs/appointments-for-laravel/dtos-and-actions
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)

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

```php
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 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)
