Skip to content

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).

TypeDescriptionExample
constFixed annual date (negative day = from end)Christmas (Dec 25, every year)
fixedOne-time eventA wedding on Jun 15, 2025
dailyEvery day, or every N daysA daily stand-up
weeklyOne or more weekdays each weekMon / Wed / Fri
monthlyA day of the month (-1 = last day)Payday (15th) · Rent (last)
nth-weekdayNth weekday of a month — or every month1st Monday of every month
formulaComputed from a registered formulaLast 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 days

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)" }

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 May

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.

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).

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).

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.

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" } }

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.

For each occurrence the value is resolved in this order — later wins:

  1. base field on the event
  2. templates (date-derived)
  3. 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.

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 is 0.
  • A day’s events array is sorted highest-first, so events[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.

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 types
cal.search().status("confirmed").getEvents(); // hide tentative / cancelled
cal.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.