---
title: "Recurring appointments — Appointments for Laravel | Roundly"
description: "Daily, weekly or monthly series with interval, count, until and weekday rules — created all-or-nothing and linked by a group UUID."
url: https://roundly-consulting.com/open-source/docs/appointments-for-laravel/recurring-appointments
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)

# Recurring appointments

Give the builder a RecurrenceData rule and call createRecurring(). Each occurrence is materialised as its own appointment, linked by a shared recurrence\_group UUID:

```php
use RoundlyConsulting\Appointments\DataTransferObjects\RecurrenceData;
use RoundlyConsulting\Appointments\Enums\Frequency;
use RoundlyConsulting\Appointments\Facades\Appointments;

$series = Appointments::schedule('Weekly standup')
    ->startingAt('2026-07-06 09:00')
    ->lasting(30)
    ->withParticipant($team)
    ->recurring(new RecurrenceData(Frequency::Weekly, count: 8, byWeekday: [1])) // Mondays
    ->createRecurring();                // Collection<int, Appointment>
```

## Rule options

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `frequency` | Frequency | — | Frequency::Daily, Frequency::Weekly or Frequency::Monthly. |
| `interval` | int | 1 | Every Nth day, week or month — at least 1. |
| `until` | ?CarbonImmutable | null | Last allowed start, inclusive. A value at midnight is read as a date and includes that whole day. |
| `count` | ?int | null | Maximum number of occurrences — at least 1; capped by recurrence.max\_occurrences. |
| `byWeekday` | list of int | \[\] | ISO weekdays that occur in each active day, week or month — 1 = Monday … 7 = Sunday. |

## Examples

```php
use Carbon\CarbonImmutable;
use RoundlyConsulting\Appointments\DataTransferObjects\RecurrenceData;
use RoundlyConsulting\Appointments\Enums\Frequency;

new RecurrenceData(Frequency::Daily, count: 5);                   // five consecutive days
new RecurrenceData(Frequency::Daily, interval: 2, count: 3);      // every other day, three times
new RecurrenceData(Frequency::Weekly, interval: 2, until: CarbonImmutable::parse('2026-12-31 23:59'));
new RecurrenceData(Frequency::Monthly, count: 3);                 // from Jan 31: Feb 28, then Mar 31 — no overflow
new RecurrenceData(Frequency::Weekly, count: 4, byWeekday: [1, 3]); // Mondays and Wednesdays
new RecurrenceData(Frequency::Weekly, interval: 2, count: 6, byWeekday: [1, 3]); // Monday and Wednesday of every other week
new RecurrenceData(Frequency::Monthly, count: 8, byWeekday: [1]);  // every Monday of each month
new RecurrenceData(Frequency::Daily, until: CarbonImmutable::parse('2026-07-31')); // midnight: a date, July 31 included

new RecurrenceData(Frequency::Daily, interval: 0);                 // throws InvalidRecurrenceException
```

## How a series is built

- Every occurrence carries everything a single create() would — participants, meta, location, coordinates, guest contacts and its own approval request.
- The series is all-or-nothing: it is created in one database transaction, so if any occurrence fails — for example with a SchedulingConflictException under preventConflicts() — none of it is kept, and since events wait for the commit, no AppointmentCreated or participant event fires for any of it.
- The rule is expanded in the appointment’s timezone, so a weekly 09:00 stays 09:00 local across a daylight-saving change.
- Without a weekday filter, the first occurrence is the start itself and each next one is interval days, weeks or months on. Monthly steps never overflow: a series starting on the 31st lands on the last day of shorter months.
- With byWeekday, every listed weekday of each active period occurs, from the start on — the period being the day, the week (Monday-based) or the month the frequency names, and every interval-th period being active. interval: 2 with byWeekday: \[1, 3\] is Monday and Wednesday of every other week; Frequency::Monthly with byWeekday: \[1\] is every Monday of each active month.
- until is inclusive — an occurrence starting exactly at until is kept. An until at midnight is read as a date and includes that whole day.
- An interval or count below one, or a weekday outside 1–7, throws InvalidRecurrenceException when the RecurrenceData is built.
- Without count or until, a rule expands to recurrence.max\_occurrences (365 by default) — always bound open-ended rules. count is capped by the same limit.

## From a DTO

Appointments::createRecurring() takes an AppointmentData and a rule — or falls back to the DTO’s own recurrence. With no rule at all it creates the single appointment:

```php
use RoundlyConsulting\Appointments\DataTransferObjects\RecurrenceData;
use RoundlyConsulting\Appointments\Enums\Frequency;
use RoundlyConsulting\Appointments\Facades\Appointments;

$series = Appointments::createRecurring($appointmentData, new RecurrenceData(Frequency::Daily, count: 5));
$series = Appointments::createRecurring($appointmentData); // falls back to $appointmentData->recurrence
```

## Working with a series

Load a series by its recurrence\_group, or preview the start times a rule produces with Appointments::occurrences() — nothing is written, and the same max\_occurrences cap applies:

```php
use Carbon\CarbonImmutable;
use RoundlyConsulting\Appointments\Facades\Appointments;
use RoundlyConsulting\Appointments\Models\Appointment;

// every appointment of one series
Appointment::query()
    ->where('recurrence_group', $series->first()->recurrence_group)
    ->ordered()
    ->get();

// preview the start times a rule produces, without writing anything
Appointments::occurrences(CarbonImmutable::parse('2026-07-06 09:00'), $rule); // list<CarbonImmutable>
```

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