NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #1525 most downloaded on Packagist
Generate add to calendar links for Google, iCal and other calendar systems
Last release 1 months ago
20 Aug 2026
Release timing varies
gaps range from 9 days to 13 months
Most releases are documented
notes for 35 of 41 stable releases
Nothing withdrawn
no release was ever pulled
9 years old
41 releases · first in 2017
TL;DR : no upgrade steps, the public API is unchanged. Headline additions are guests and separate start/end timezones. ICS files carrying a timezone n
TL;DR: no upgrade steps, the public API is unchanged. Headline additions are guests and separate start/end timezones. ICS files carrying a timezone now name it with a TZID and ship a VTIMEZONE. Subclassing a generator? Read the last section.
Link::guest() / Link::guests(), required and optional roles, rendered as add (Google), inv_list (Yahoo), to/cc (Outlook), ATTENDEE (ICS) (#229, #237)TRANSP, CLASS, RRULE, X-MICROSOFT-CDO-BUSYSTATUS, DTSTAMP via the options array (#230) (#244)additionalCalendarProperties(), additionalEventProperties() (#242, #247)VTIMEZONE for every TZID they name, without which older Outlook desktop shifts the event (#249)TZID, not a UTC instant, which used to put every occurrence past a DST change an hour off (#254)+02:00, CEST, POSIX rule sets, all of Etc/) are written in UTC (#248)REMINDER and format are validated instead of silently falling back, so ['format' => 'FILE'] no longer returns a link where a file was asked for (#254)protected, Google/Yahoo read BASE_URL late statically (#242)false URL parameter renders as &key, not &key=UID, PRODID, URL, RRULE, the enumerated options, and ATTENDEE addresses assigned straight to Link::$guests (#245, #254)charset=utf-8, invalid control characters are dropped (#250)VALARM (#247)VTIMEZONE stopped at the year it described. The last change of each kind now carries a yearly rule where the zone repeats predictably (#254)view=d&type=20, wall clock ST/ET, DUR=allday for single days, exclusive ET for multi-day (#232)CAL-ADDRESS is a URI, not TEXT. Unusable guest addresses are rejected (#229)'0' is no longer dropped as emptyThe public API is unchanged, but three changes reach a subclass. The first two are fatal at load time:
Ics::$presentationOptions is now typed array. Redeclaring it untyped raises Type of Sub::$presentationOptions must be array. Drop the redeclaration or type it to match.Ics::generateAlertComponent(), Yahoo::sanitizeText(), Yahoo::sanitizeAddressList() and BaseOutlook::sanitizeString() went private to protected. A same-named private method raises Access level to Sub::sanitizeText() must be protected or weaker. Widen it.Yahoo::BASE_URL dropped view=d&type=20, so a subclass building its own URL from the constant gets the shorter value.Made by @alies-dev and the community.
Full Changelog: 2.0.1...2.1.0
One column per quarter.
Link::guest() and Link::guests(), with required and optional roles. Guests are rendered as add for Google, inv_list for Yahoo, to and cc for Outlook, and ATTENDEE properties in ICS (#229)Link::$fromTimezone, Link::$toTimezone and Link::hasDistinctTimezones(). Google links use stz and etz, ICS files write each endpoint with its own TZID alongside a matching VTIMEZONE component, as RFC 5545 requires. Two spellings of one zone (UTC and Etc/UTC, or the bare Z that PHP leaves on a Z suffixed ISO string) count as a single zone, so only an event that genuinely crosses zones takes this path (#233, #246, #249)TRANSP, CLASS, RRULE and X-MICROSOFT-CDO-BUSYSTATUS via the options array (#230)DTSTAMP via the options array. It defaults to the event start, which keeps the same Link producing the same bytes, so pass your own value when you need it read as a revision signal (#244)$urlParameters is rendered as a repeated query parameter (#228)additionalCalendarProperties() and additionalEventProperties() extension points, emitted before the nested VALARM component so the output stays valid (#242, #247)Ics::generateAlertComponent(), Yahoo::sanitizeText(), Yahoo::sanitizeAddressList() and BaseOutlook::sanitizeString() are now protected, and Google/Yahoo read BASE_URL late statically so a subclass can redefine itTZID, with a matching VTIMEZONE, where it used to be written as a UTC instant. An RRULE repeats the local time of its DTSTART, so a start pinned to UTC put every occurrence past a daylight saving change an hour away from the time the event was booked for. A recurring event in UTC, in a zone that keeps one offset the year round, or in a zone that names no place is unaffected, and so is any event without an RRULEREMINDER now has to be an array, its TIME a DateTimeInterface and its DESCRIPTION a string. A value of another type used to be dropped for the default alarm without a word, which handed back a reminder nobody asked forformat now has to be html or file. Anything else used to fall through to the data URI, so ['format' => 'FILE'] silently returned a link rather than the file it asked forfalse parameter value as a bare flag (&key) rather than as an empty assignment (&key=)Link::guest() and Link::guests() now ignore an address that is already on the guest list, compared without regard to case, so nobody is invited twiceATTENDEE address can no longer inject calendar properties. guest() rejects a control character, but Link::$guests is a public property that can be assigned around it, and a CR or an LF in an address ended the ATTENDEE property and started another. Control characters are now percent encoded into the mailto URIURL and RRULE now reject every control character rather than only CR and LF. Neither a URI nor a recurrence rule admits one, and writing it out produced a file no parser acceptsVTIMEZONE components now carry a yearly recurrence rule on the last change of each kind, whenever the zone repeats that change on a fixed weekday of a fixed month. Without one the component stopped at the year it described, and a recurring event past that resolved against the wrong offset for part of every later year on a client that does not resolve bare IANA names'0' is no longer dropped from the output as if it were empty+02:00, an abbreviation such as CEST, the POSIX rule sets, and the whole Etc/ tree) are written in UTC instead of being named. Google silently ignores a ctz it cannot resolve, which left the event at the wrong instant for every viewer outside that offset, and an offset in an ICS TZID cut the property value in half at its colon. Parsing an ISO 8601 string with an offset is the everyday way to reach this (#248)UID and PRODID are escaped as the TEXT values they are, URL and RRULE reject a carriage return or line feed, the enumerated properties are checked against their allowed tokens, and a custom REMINDER.DESCRIPTION is escaped like the default one (#245)charset=utf-8 rather than the unregistered utf8, and control characters invalid in a TEXT value are dropped (#250)view=d&type=20 pair, timed events send wall clock ST and ET values instead of UTC, single day all-day events send DUR=allday, and multi day all-day events send an exclusive ET end date (#232)CAL-ADDRESS is a URI, not TEXT) and reject guest email addresses that no calendar service can carry (#229)DUR=allday and an ET end date cannot be sent together (#243)The public API is unchanged, but two of the changes above are visible to a class extending a generator, and either can stop it loading:
Ics::$presentationOptions is now declared array, where 2.0.x left it untyped. A subclass that redeclares the property without a type raises Type of Sub::$presentationOptions must be array. Drop the redeclaration, or type it array to matchIcs::generateAlertComponent(), Yahoo::sanitizeText(), Yahoo::sanitizeAddressList() and BaseOutlook::sanitizeString() went from private to protected. A subclass that happens to declare a private method of the same name raises Access level to Sub::sanitizeText() must be protected or weaker. Widen it to protected, which is what it now overridesYahoo::BASE_URL no longer carries the stale view=d&type=20 pair. A subclass that built its own URL from the constant gets the shorter valueUpdate the webOffice() link domain from outlook.office.com to outlook.cloud.microsoft . The old domain returned AADSTS90015: Requested query string is
Fix ics alert not working when a date is set #202 by @giacomovernoni
Full Changelog: 1.11.0...1.11.1
Allow URL customisations for Google, Outlook and Yahoo by @alies-dev in #191
Full Changelog: 1.10.0...1.11.0
Add reminder functionality to ICS by @S-Braeutigam and @alies-dev in #178 1ffc15a
Full Changelog: 1.9.0...1.10.0
ICS: support different presentation ( file , html ) by @alies-dev in #185
file, html) by @alies-dev in #185Full Changelog: 1.8.6...1.9.0
fix: All-day event issues by @atymic in #184
Nothing published for this version
Nothing published for this version
Nothing published for this version
ICS: Use DESCRIPTION instead of X-ALT-DESC (as it has better support) by @cdubz in #158
DESCRIPTION instead of X-ALT-DESC (as it has better support) by @cdubz in #158### Changed - Remove PHP 7.4 support - Update dependencies
ICS: Add PRODID and DTSTAMP required parameters to make ICS valid by @makbeta
PRODID and DTSTAMP required parameters to make ICS valid by @makbetaWebOffice and WebOutlook into a parent class by @lptnOutlook: Fixed #148 Support HTML-formatted description by @dravenk in #150
Outlook: Fixed location field characters (by @dravenk in #144)
New: Add support for outlook.office.com $link->webOffice(); (@dravenk and @gulios)
from and to dates and is the same for both (@bradyemerson)Drop support for PHP 7.2 and PHP 7.3
ICS: support URLs as option (@gulios)
Yahoo link doesn’t work (yahoo changed param names) (@mukeshsah08).
Google: use UTC timezone to bypass problems with some timezone names unsupported by Google calendar (⚠️ backwards-incompatible if you extended Google
Simplify extending of ICS Generator
Simplify extending of WebOutlook (e.g. for Office365)
allday parameter only for a single-day eventsInvalidLink now extends \InvalidArgumentExceptionAllow specifying custom UID ICS links
UID ICS links (https://github.com/spatie/calendar-links/pull/85)\DateTimeImmutable::class)Support custom generators ($link->formatWith(new Your\Generator())
$link->formatWith(new Your\Generator())Fix Google links for all-day events (use next day as end-date for single-day events)
enddt for single-day events)Link::createAllDay static constructor to simplify creating of all-day eventsFix iCal all day links (use DURATION according RFC 5545)
Fix Yahoo links for multiple days events
Fix iCal: Use CRLF instead of LF (according RFC 5545)
; character (according RFC 5545)- Support timezones - Add outlook.com link generator
- Fix Yahoo links
- Add all day support
- Fix newlines in description
- Fix for iCal links in Safari
Use \n instead of %0A when generating an ics file
\n instead of %0A when generating an ics file- initial release
Nothing published for this version
Nothing published for this version
Nothing published for this version
Fix all-day events via constructor being 1 day short by @freekmurze in #211
Full Changelog: 2.0-alpha.1...2.0-beta.1
ICS: allow PRODID customization by @alies-dev
API changes:
Link::create(...) doesn't have a 4th parameter to declare event as a full-day. Instead, use Link::createAllDay(...)If you extended classes from the package or used non-private members directly, these BC changes may be breaking for you:
Spatie\CalendarLinks\Link#__get() was removedSpatie\CalendarLinks\Link#$title changed from having no type to stringSpatie\CalendarLinks\Link#$from changed from having no type to DateTimeImmutableSpatie\CalendarLinks\Link#$to changed from having no type to DateTimeImmutableSpatie\CalendarLinks\Link#$description changed from having no type to stringSpatie\CalendarLinks\Link#$description changed default value from NULL to ''Spatie\CalendarLinks\Link#$allDay changed from having no type to boolSpatie\CalendarLinks\Link#$address changed from having no type to stringSpatie\CalendarLinks\Link#$address changed default value from NULL to ''__construct() of class Spatie\CalendarLinks\Link became finalSpatie\CalendarLinks\Link::create() changed from no type to staticSpatie\CalendarLinks\Link::createAllDay() changed from self to the non-covariant staticSpatie\CalendarLinks\Link::createAllDay() changed from self to staticSpatie\CalendarLinks\Link::createAllDay() changed name from fromDate to fromSpatie\CalendarLinks\Link#description() changed from no type to staticSpatie\CalendarLinks\Link#address() changed from no type to staticSpatie\CalendarLinks\Exceptions\InvalidLink::invalidDateRange() was removedSpatie\CalendarLinks\Generators\Yahoo#$dateFormat was removedSpatie\CalendarLinks\Generators\Yahoo#$dateTimeFormat was removedSpatie\CalendarLinks\Generators\WebOffice became finalSpatie\CalendarLinks\Generators\WebOffice::BASE_URL was removedSpatie\CalendarLinks\Generators\BaseOutlook#$dateFormat was removedSpatie\CalendarLinks\Generators\BaseOutlook#$dateTimeFormat was removedSpatie\CalendarLinks\Generators\BaseOutlook#$urlParameters was removedSpatie\CalendarLinks\Generators\WebOffice::BASE_URL visibility reduced from protected to privateSpatie\CalendarLinks\Generators\WebOffice visibility reduced from public to protectedSpatie\CalendarLinks\Generators\Ics#$dateFormat changed from having no type to stringSpatie\CalendarLinks\Generators\Ics#$dateTimeFormat changed from having no type to stringSpatie\CalendarLinks\Generators\Ics#$options changed from having no type to arraySpatie\CalendarLinks\Generators\BaseOutlook#$dateFormat was removedSpatie\CalendarLinks\Generators\BaseOutlook#$dateTimeFormat was removedSpatie\CalendarLinks\Generators\BaseOutlook visibility reduced from public to protectedSpatie\CalendarLinks\Generators\WebOutlook became finalSpatie\CalendarLinks\Generators\WebOutlook::BASE_URL was removedSpatie\CalendarLinks\Generators\BaseOutlook#$dateFormat was removedSpatie\CalendarLinks\Generators\BaseOutlook#$dateTimeFormat was removedSpatie\CalendarLinks\Generators\BaseOutlook#$urlParameters was removedSpatie\CalendarLinks\Generators\WebOutlook::BASE_URL visibility reduced from protected to privateSpatie\CalendarLinks\Generators\WebOutlook visibility reduced from public to protectedSpatie\CalendarLinks\Generators\Google#$dateFormat was removedSpatie\CalendarLinks\Generators\Google#$dateTimeFormat was removedFull Changelog: 1.11.0...2.0-alpha.1
InvalidLink::invalidDateRange() removed — the deprecated method has been deleted; use InvalidLink::negativeDateRange() instead
TL;DR: for 95% installations, no actions required. The biggest change: Link::create() is not longer has 3rd parameter allDay: you need to use Link::createAllDay() instead.
$from and $to are now DateTimeImmutable — previously DateTime$title, $from, $to, and $allDay are public readonly; $description and $address are public. The magic __get() method has been removed.Link::create() no longer accepts $allDay parameter — use Link::createAllDay() for all-day events insteadLink::createAllDay() end date semantics changed — the $numberOfDays parameter now represents inclusive days (e.g., createAllDay('Event', $date, 1) creates a single-day event). The constructor now internally adds +1InvalidLink::invalidDateRange() removed — the deprecated method has been deleted; use InvalidLink::negativeDateRange() insteadBaseOutlook::baseUrl() visibility changed — from public to protectedWebOffice and WebOutlook classes are now final$dateFormat, $dateTimeFormat in Google, Yahoo, and BaseOutlook are now private const values, preventing override via subclassingPRODID — the Ics generator now accepts a PRODID option to customize the product identifier in generated ICS contentLink::createAllDay() returns static — previously returned self, now supports late static binding for subclassesdeclare(strict_types=1) added to all source files#[\Override] attributesDateTimeImmutable used throughout instead of mutable DateTime#[Test] attributes replace @test annotationsMade by @alies-dev and the community.
Full Changelog: 1.11.1...2.0
Your coding agent can read these notes before it upgrades. Set up the MCP server →