Event types
The core ships an event type for every frequency — const, fixed, daily,
weekly, monthly, nth-weekday, formula, relative; plugins add more (such
as lunar, liturgical, hijri).
| Type | Description | Example |
|---|---|---|
const | Fixed annual date (negative day = from end) | Christmas (Dec 25, every year) |
fixed | One-time event | A wedding on Jun 15, 2025 |
daily | Every day, or every N days | A daily stand-up |
weekly | One or more weekdays each week | Mon / Wed / Fri |
monthly | A day of the month (-1 = last day) | Payday (15th) · Rent (last) |
nth-weekday | Nth weekday of a month — or every month | 1st Monday of every month |
formula | Computed from a registered formula | Last Monday of May |
{ type: "const", id: "christmas", month: 12, day: 25, title: "Christmas" }{ type: "fixed", id: "wedding", year: 2025, month: 6, day: 15, title: "Wedding" }Every day, or every interval days (phased off startDate when given):
{ type: "daily", id: "standup", title: "Stand-up" } // every day{ type: "daily", id: "meds", interval: 3, startDate: "2025-01-01", title: "Meds" } // every 3 daysmonthly
Section titled “monthly”A day of the month. A negative day counts from the end — -1 is the last
day of every month, -2 the second-to-last:
{ type: "monthly", id: "payday", day: 15, title: "Payday" }{ type: "monthly", id: "rent", day: -1, title: "Rent (last day)" }nth-weekday
Section titled “nth-weekday”The nth weekday of a month. Omit month to repeat it every month; nth: -1
is the last occurrence:
{ type: "nth-weekday", id: "retro", nth: 1, dayOfWeek: 1, title: "Retro" } // 1st Monday, every month{ type: "nth-weekday", id: "mothers", nth: 2, dayOfWeek: 0, month: 5, title: "Mother's Day" } // 2nd Sun of Mayformula
Section titled “formula”A formula event references a formula registered by a plugin. If the formula is
not registered, generation throws — misconfiguration fails loudly rather
than silently producing no events.
Common properties
Section titled “Common properties”Every event supports: id (required), type (required), title,
description, allDay, startTime, endTime, location, url,
source, categories, priority, status, color, reminders,
metadata, startDate, endDate, templates, exceptions, overrideDates.
Bounding a recurrence — startDate / endDate
Section titled “Bounding a recurrence — startDate / endDate”Any recurring event can be limited to a window with startDate and endDate
(YYYY-MM-DD, both bounds inclusive). Occurrences before startDate or after
endDate are dropped. This window — and the year bounds startYear /
endYear / excludeYears (the builder’s .between(2025, 2030) / .until(2030) /
.exceptYears(2027)) — is applied centrally, so it works for every type:
const, daily, weekly, monthly, nth-weekday, formula, and plugin types
alike.
// A weekly stand-up, but only for July 2026{ type: "weekly", id: "standup", dayOfWeek: 1, title: "Stand-up", startDate: "2026-07-01", endDate: "2026-07-31",}Leave either bound off for an open-ended range (only a start, or only an end).
Customizing a single occurrence
Section titled “Customizing a single occurrence”A recurring event isn’t all-or-nothing — individual occurrences can be skipped,
replaced, moved, or given values derived from their own date. These keys live on
the event config (each is keyed by the occurrence’s original computed date,
YYYY-MM-DD).
Skip / override one date — exceptions
Section titled “Skip / override one date — exceptions”Maps iCalendar EXDATE / RECURRENCE-ID.
{ type: "weekly", id: "mass", dayOfWeek: 0, title: "Sunday Mass", exceptions: { "2026-07-12": { skip: true }, // EXDATE — drop this occurrence "2026-12-27": { // RECURRENCE-ID — keep it, but replace properties for this one instance override: { title: "Christmas Octave", url: "https://x.com/christmas" }, }, },}{ skip: true } removes the occurrence; { override } keeps it and replaces any
base properties (title, url, description, metadata, priority, …) for
that date only.
Move one date — overrideDates
Section titled “Move one date — overrideDates”Force-reschedule a computed date to another ({ "from": "to" }), e.g. a holiday
observed on a different day.
{ type: "const", id: "founders", month: 2, day: 18, overrideDates: { "2026-02-18": "2026-02-20" } }Date-derived fields — templates
Section titled “Date-derived fields — templates”For values that change with the date (a per-date link, a dated title), give a
templates map of field → template. Tokens come from the occurrence date:
{YYYY} {MM} {DD} (zero-padded) · {M} {D} (no padding).
{ type: "weekly", id: "mass", dayOfWeek: 0, title: "Sunday Mass", templates: { url: "https://x.com/mass/{D}-{M}" },}// 2026-07-07 → https://x.com/mass/7-7// 2026-07-14 → https://x.com/mass/14-7 (use {DD}/{MM} for 07 / 14-07)Any string field works (url, title, description, location, …). The
interpolateDateTokens(template, date) helper is exported if you need it directly.
Precedence
Section titled “Precedence”For each occurrence the value is resolved in this order — later wins:
- base field on the event
templates(date-derived)exceptions[date].override(this specific date)
A skip or an out-of-window date (validity window)
drops the occurrence before any of this. Since the ICS exporter
expands one VEVENT per occurrence, the resolved per-occurrence values export
as-is — no RRULE needed.
Precedence — multiple events on one day
Section titled “Precedence — multiple events on one day”Several events can fall on the same day — a birthday, an anniversary, a feast, a
holiday — especially once you add plugins or subscribe to multiple sources.
calendaryjs orders them by a single number, priority:
- Higher = on top, exactly like CSS
z-index. Default is0. - A day’s
eventsarray is sorted highest-first, soevents[0]is the “primary” event for that day. - The engine assigns no meaning to the numbers — you do. Want a date to win?
Give it
priority: 100. Build your own tiers on top if you like. - Ties keep generation order (stable sort), so the output is reproducible.
cal.addGroup({ id: "sep", events: [ { type: "const", id: "birthday", month: 9, day: 2, title: "Birthday", priority: 1, }, { type: "const", id: "national-day", month: 9, day: 2, title: "National Day", priority: 100, }, ],});
cal.getDay("2025-09-02").events[0].title; // "National Day" (priority 100 beats 1)Tag each event with a source (the plugin, feed, or ICS subscription it came
from) to adjust priority — or filter — in bulk, like toggling a calendar in
Google or Apple Calendar.
Filtering — by type, status, source
Section titled “Filtering — by type, status, source”Beyond text and categories, search() filters on the fields the model already
carries. Each takes one or more values and matches any:
cal.search().type("weekly", "daily").getEvents(); // only these event typescal.search().status("confirmed").getEvents(); // hide tentative / cancelledcal.search().source("work-feed").getEvents(); // one subscription / feed
// getDays() filters by type too:cal.getDays({ from: "2025-01-01", to: "2025-01-31", types: ["weekly"] });Every computed event carries its originating type, so you can group or style a
day’s events by kind in your UI.