Plugin screenshot thumbnail 1/1

A comprehensive events and calendar plugin for Craft CMS 5 — recurrence, multisite, ICS feeds, a frontend query API, GraphQL, and native Commerce ticketing.

Owl is a maintained, fairly-priced alternative to Solspace Calendar: the recurrence, ICS, and front-end submission features other plugins paywall are included in the free Lite edition, and the Pro edition ($149) adds first-class Craft Commerce ticketing.

Editions

Lite (free)Pro
Event element, unlimited calendars, per-calendar field layouts
Recurrence (RRULE) + exceptions + single-occurrence overrides
Month/week/day/list views, FullCalendar demo templates
Twig query API + GraphQL
ICS export + subscription feeds
Front-end event submission
Multisite
Commerce ticketing (tickets, ticket types, capacity)

Architecture

Owl stores each event's canonical RRULE and materialises occurrences into an indexed table (generated to a rolling horizon by a queue job), so calendar range queries and pagination are plain, fast SQL — and per-occurrence ticketing/inventory has a real row to attach to. See PLAN.md for the full design.

The recurrence engine (src/recurrence) is deliberately framework-agnostic and unit-tested in isolation, including DST spring-forward/fall-back correctness.

Front-end

Query events in Twig:

{% set upcoming = craft.owl.events
    .startsAfter(now)
    .orderBy('startDate ASC')
    .limit(10)
    .all() %}

Feed a JS calendar (FullCalendar) from the JSON endpoint, which returns only the occurrences overlapping the requested range:

GET /owl/events.json?start=2026-07-01&end=2026-08-01&calendar=concerts

Subscribe to or download ICS:

/owl/calendar/<handle>.ics   # a calendar's subscribable feed
/owl/event/<id>.ics          # a single event

Tickets (Pro)

With the Pro edition and Craft Commerce installed, events can sell tickets. Enable "Sell tickets" on a calendar, then Manage tickets on an event to add ticket types (name, price, optional capacity). Each ticket is a Commerce purchasable:

{% for ticket in craft.owl.tickets.getTicketsForEvent(event.id) %}
    {{ ticket.ticketName }} — {{ ticket.price|commerceCurrency(ticket.store.currency) }}
    {% if ticket.remaining is not null %}({{ ticket.remaining }} left){% endif %}
{% endfor %}

Capacity is enforced by Owl: a sold-out ticket reports getIsAvailable() as false, and the sold count is incremented transactionally when an order completes.

Maintenance

Occurrences are materialised up to a rolling horizon (default 24 months). Run the regenerate command from a daily cron so open-ended recurrence rules keep extending forward:

0 3 * * * cd /path/to/project && php craft owl/maintenance/regenerate >> /dev/null 2>&1

Add --queue to push the work onto Craft's queue instead of running it inline (recommended for sites with many recurring events).

Development

PHP and Composer run inside DDEV:

ddev start
ddev composer install --no-plugins   # --no-plugins: the craft-plugin installer expects a host app
ddev exec vendor/bin/pest --testsuite=Unit   # framework-agnostic unit tests (recurrence, ICS)

The recurrence engine and ICS builder are framework-agnostic and covered by the Pest unit suite.

The Craft layer (elements, services, queries, occurrence materialisation, timezone/DST storage) is covered by an integration harness that runs inside a real Craft install:

ddev craft owl/test/run   # exits non-zero on failure

markhuot/craft-pest is not yet compatible with Craft 5.9's Symfony 7 dependencies, so the integration coverage is provided via this console harness rather than Pest feature tests.

License

This plugin is licensed under the Craft License — the standard license for commercial Craft CMS plugins. A commercial license is required for use in a production environment. © 2026 Justin Holt.

Standard

Pro

Plus $74/year after one year.

Installation Instructions

To install this plugin, copy the command above to your terminal.

Reviews

This plugin doesn't have any reviews.

Active Installs
0
Version
5.1.0
License
Craft
Compatibility
Craft 5
Last release
July 19, 2026
Activity (30 days)
0
Closed Issues
0
Open Issues
0
Merged PRs
0
Open PRs