Calendar export (ICS)
Generate RFC 5545 calendar text for one appointment or a collection. The package stays HTTP-free — it returns a string and you decide how to deliver it:
use RoundlyConsulting\Appointments\Facades\Appointments;
$ics = Appointments::for($appointment)->ics(); // one event — or $appointment->toIcs()
$feed = Appointments::ics($user->appointments()->upcoming()->get()); // one VCALENDAR, many VEVENTs
return response($feed, 200, [
'Content-Type' => 'text/calendar; charset=utf-8',
'Content-Disposition' => 'attachment; filename="appointments.ics"',
]);Sample output
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Roundly Consulting//Appointments for Laravel//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:[email protected]
DTSTAMP:20260615T120000Z
DTSTART:20260701T090000Z
DTEND:20260701T100000Z
SUMMARY:Clinic visit
STATUS:TENTATIVE
DESCRIPTION:Bring your previous test results
LOCATION:Clinic A
GEO:51.5074;-0.1278
ORGANIZER;CN=Front desk:mailto:[email protected]
ATTENDEE;CN=Alex Example:mailto:[email protected]
END:VEVENT
END:VCALENDARWhat goes into each event
| Property | Source |
|---|---|
UID | The appointment’s stored random uuid at your app.url host (…@example.com) — unique across apps and stable across a re-seeded database. |
DTSTART / DTEND | starts_at / ends_at in UTC; a missing end is derived from the duration. |
SUMMARY | name. |
DESCRIPTION | description, when set. |
STATUS | TENTATIVE for pending; CONFIRMED for confirmed and completed; CANCELLED for cancelled, declined and no-show. |
LOCATION | The location column, falling back to meta.location. |
GEO | latitude;longitude, when coordinates are set. |
ORGANIZER | The appointment’s primary email contact; CN is the contact’s name, else its label. |
ATTENDEE | One per participant whose model has a primary email; CN is the model’s name attribute. |
Participants without a primary email are left out: an ATTENDEE must be a calendar address — in practice mailto: — and calendar clients have nothing to act on without one. Add HasContacts to participant models to include them (see Guest contacts).
Escaping and folding
Text values are escaped per RFC 5545 §3.3.11: any line break — a textarea’s CRLF, a lone CR or LF — becomes one escaped \n; backslashes, semicolons and commas are backslash-escaped; other control characters except tab are dropped. CN values lose double quotes and control characters and are quoted when they contain a colon, semicolon or comma. Long lines fold at 75 octets without splitting a multi-byte UTF-8 character, and every line ends in CRLF.
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.