el-schneider / statamic-calendar
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 3
pkg:composer/el-schneider/statamic-calendar
Requires
- rlanvin/php-rrule: ^2.6
- statamic/cms: ^5.0 || ^6.0
Requires (Dev)
- laravel/pint: ^1.27
- orchestra/testbench: ^9.0
- pestphp/pest: ^4.3
- pestphp/pest-plugin-laravel: ^4.0
README
Statamic Calendar
Recurring events and cached occurrences for Statamic.
Features
- Define complex recurrence patterns using RFC 5545 (RRULE) via
rlanvin/php-rrule - Materialize occurrences into Laravel cache for fast listings
- Antlers tags for listing, current occurrence, next occurrences, and month grid
- Month calendar view — server-rendered, navigable via query params, no JS required
- JSON REST API for JS-based calendar components (opt-in)
- iCalendar (.ics) feed for calendar app subscriptions + per-event "Add to calendar" downloads
- Two URL strategies: query string (default, Statamic-native) or date segments
- Example templates for index (list, archive, calendar grid) and show pages
Installation
composer require el-schneider/statamic-calendar
Publish the config:
php artisan vendor:publish --tag=statamic-calendar
URL Strategies
The addon supports two strategies for occurrence URLs. Configure in config/statamic-calendar.php:
Query String (default)
Uses Statamic's native collection routing. The addon doesn't register any routes — your collection config handles everything:
# content/collections/events.yaml route: '/events/{slug}'
Occurrence URLs look like /events/my-event?date=2025-03-15.
Date Segments (opt-in)
For SEO-friendly date-based URLs like /calendar/2025/03/15/my-event. Enable in config:
'url' => [ 'strategy' => 'date_segments', 'date_segments' => [ 'prefix' => 'calendar', ], ],
The addon registers a route at /{prefix}/{year}/{month}/{day}/{slug}.
iCalendar (.ics) Export
The addon exposes an .ics feed that calendar apps (Apple Calendar, Google Calendar, Outlook) can subscribe to. Enabled by default.
Subscribable Feed
GET /calendar.ics — returns all cached occurrences as a standard iCalendar feed. Calendar apps can subscribe to this URL and will receive updates as events change.
Single-Event Download
GET /calendar.ics/{occurrenceId} — returns a single occurrence as a downloadable .ics file. Use this for "Add to calendar" buttons. The response includes a Content-Disposition: attachment header.
Configuration
// config/statamic-calendar.php 'ics' => [ 'enabled' => true, // set to false to disable .ics routes 'feed_url' => '/calendar.ics', 'calendar_name' => env('APP_NAME', 'Calendar'), ],
Template Usage
{{-- Subscribe link --}}
<a href="{{ calendar:ics_url }}">Subscribe to calendar</a>
{{-- Per-event download inside a calendar loop --}}
{{ calendar from="now" limit="10" }}
<a href="{{ calendar:ics_download_url :occurrence_id="id" }}">
Add to calendar
</a>
{{ /calendar }}
REST API
An opt-in JSON API for occurrences, designed for JS-based calendar components (FullCalendar, Toast UI Calendar, custom Alpine/Vue/React widgets, etc.) that build their view client-side.
Enable
Set the env var or publish the config:
STATAMIC_CALENDAR_API_ENABLED=true
// config/statamic-calendar.php 'api' => [ 'enabled' => env('STATAMIC_CALENDAR_API_ENABLED', false), 'route' => env('STATAMIC_CALENDAR_API_ROUTE', 'api/calendar/occurrences'), 'middleware' => env('STATAMIC_CALENDAR_API_MIDDLEWARE', 'api'), ],
Endpoint
GET /api/calendar/occurrences
| Parameter | Type | Description | Default |
|---|---|---|---|
from |
date |
Start date (ISO 8601 or Y-m-d) |
now |
to |
date |
End date | — |
limit |
int |
Max occurrences | — |
sort |
string |
asc or desc |
asc |
tags |
string |
Comma-separated tag slugs | — |
organizer |
string |
Organizer entry ID | — |
Response
{
"data": [
{
"id": "abc-123-2026-03-06-150000",
"entry_id": "abc-123",
"title": "Laracon Online",
"slug": "laracon-online",
"teaser": "The best Laravel conference",
"organizer_id": "org-456",
"organizer_slug": "laravel-org",
"organizer_title": "Laravel",
"organizer_url": "/organizers/laravel-org",
"tags": ["tech", "laravel"],
"start": "2026-03-06T15:00:00+00:00",
"end": "2026-03-06T16:00:00+00:00",
"is_all_day": false,
"is_recurring": true,
"recurrence_description": "every week on Friday",
"url": "/events/laracon-online?date=2026-03-06"
}
]
}
Examples
// Month view fetch('/api/calendar/occurrences?from=2026-03-01&to=2026-03-31') // Week view fetch('/api/calendar/occurrences?from=2026-03-02&to=2026-03-08') // Next 5 upcoming fetch('/api/calendar/occurrences?limit=5') // Filtered by tag and organizer fetch('/api/calendar/occurrences?tags=music,art&organizer=org-123')
CORS
The API uses Laravel's api middleware group, so cross-origin requests are handled by your app's config/cors.php. Laravel's default config already allows api/* paths from all origins — adjust as needed.
Setting Up Templates
Events Index
Create a page or route that uses the {{ calendar }} tag. For example, add to routes/web.php:
Route::statamic('events', 'events/index', [ 'title' => 'Upcoming Events', ]);
Then create resources/views/events/index.antlers.html:
<h1>Upcoming Events</h1> {{ calendar from="now" limit="20" }} <a href="{{ url }}"> <h2>{{ title }}</h2> <p>{{ start format="l, M j, Y" }}</p> {{ if is_recurring }} <p>{{ recurrence_description }}</p> {{ /if }} </a> {{ /calendar }}
Event Show Page
Set the collection template to events/show, then create resources/views/events/show.antlers.html:
<h1>{{ title }}</h1> {{ calendar:current_occurrence }} <p>{{ start format="l, F j, Y" }}</p> {{ if !is_all_day }} <p> {{ start format="g:i A" }} {{ if end }}– {{ end format="g:i A" }}{{ /if }} </p> {{ else }} <p>All day</p> {{ /if }} {{ if is_recurring }} <p>Repeats: {{ recurrence_description }}</p> {{ /if }} {{ /calendar:current_occurrence }} {{ calendar:next_occurrences :entry="id" limit="5" }} <a href="{{ url }}">{{ start format="M j, Y" }}</a> {{ /calendar:next_occurrences }}
The {{ calendar:current_occurrence }} tag reads the ?date= query parameter and resolves the matching occurrence for the current entry. When using the date_segments strategy, the date is extracted from the URL instead.
Antlers Tags
{{ calendar }}
Lists occurrences from the cache (or resolves them live for non-default collections).
| Parameter | Description | Default |
|---|---|---|
from |
Start date | now |
to |
End date | — |
limit |
Max occurrences | — |
collection |
Collection handle | config value |
tags |
Filter by taxonomy terms | — |
{{ calendar:month }}
Renders a month grid with weeks, days, and occurrences. Navigation via query params, fully server-rendered. See the example index template for usage.
| Parameter | Description | Default |
|---|---|---|
param |
Query string parameter name (allows multiple calendars per page) | month |
week_starts_on |
Day of week (0 = Sunday, 1 = Monday) |
1 |
fixed_rows |
Always render 6 rows for consistent grid height | false |
collection |
Collection handle | config value |
tags |
Filter by taxonomy terms | — |
Variables available inside the tag pair: month_label, year, month, prev_url, next_url, today, day_labels (loop with label, full_label), and weeks → days → date, day, is_current_month, is_today, occurrences.
{{ calendar:current_occurrence }}
Resolves the current occurrence for the entry in context, based on the ?date= query param. Use as a tag pair — variables available inside:
start— Carbon dateend— Carbon date (nullable)is_all_day— booleanis_recurring— booleanrecurrence_description— human-readable recurrence ruleoccurrence_url— the occurrence URL
{{ calendar:next_occurrences }}
Lists upcoming occurrences for a specific entry.
| Parameter | Description | Default |
|---|---|---|
entry |
Entry ID | current context id |
from |
Start date | now |
to |
End date | — |
limit |
Max occurrences | 5 |
{{ calendar:ics_url }}
Returns the URL to the .ics calendar feed. Use it to offer a "Subscribe" link:
<a href="{{ calendar:ics_url }}">Subscribe to calendar</a>
{{ calendar:ics_download_url }}
Returns the .ics download URL for a single occurrence. Use inside any {{ calendar }} loop to offer an "Add to calendar" button:
{{ calendar from="now" limit="10" }}
<h2>{{ title }}</h2>
<a href="{{ calendar:ics_download_url :occurrence_id="id" }}">Add to calendar</a>
{{ /calendar }}
| Parameter | Description | Default |
|---|---|---|
occurrence_id |
Occurrence ID ({entry_id}-{Y-m-d-His}) |
current context id |
{{ calendar:for_organizer }}
Lists upcoming occurrences for an organizer (from cache).
| Parameter | Description | Default |
|---|---|---|
organizer |
Organizer entry ID | current context id |
limit |
Max occurrences | 5 |
Configuration
Key options in config/statamic-calendar.php:
| Key | Description | Default |
|---|---|---|
collection |
Source collection handle | events |
fields.dates |
Grid field handle | dates |
url.strategy |
query_string or date_segments |
query_string |
url.query_string.param |
Query parameter name | date |
url.date_segments.prefix |
URL prefix for date segments | calendar |
api.enabled |
Enable JSON REST API | false |
api.route |
API route path | api/calendar/occurrences |
api.middleware |
Middleware group | api |
ics.enabled |
Enable .ics feed routes | true |
ics.feed_url |
Feed URL path | /calendar.ics |
ics.calendar_name |
Calendar name in .ics output | APP_NAME |
cache.key |
Cache store key | statamic_calendar.occurrences |
cache.days_ahead |
Recurrence expansion window | 365 |
Cache
Occurrences are materialized into Laravel's cache for fast listing. The cache rebuilds automatically when entries are saved or deleted.
Manual rebuild:
php artisan occurrences:rebuild
Example Blueprint
Publish the example blueprint:
php artisan vendor:publish --tag=statamic-calendar-examples
