Skip to content

Collections & precedence

The most involved recipe: split a calendar into one collection per concern, load each into its own group, and let the engine handle precedence and provenance.

import { calendary, type Collection } from "calendaryjs";
// One collection per concern — each a portable, `.cdy`-shaped object.
const publicHolidays: Collection = {
collection: "public-holidays",
priority: 100, // whole collection on top on busy days — no per-event stamping
events: [{ type: "const", id: "newyear", month: 1, day: 1, title: "New Year" }],
};
const team: Collection = {
collection: "team",
priority: 50,
events: [
{
type: "const",
id: "kickoff",
month: 1,
day: 1,
title: "Team kickoff",
metadata: { owner: "ops" }, // your domain object rides along on the event
exceptions: { "2026-01-01": { skip: true } }, // cancelled this one year
},
],
};
const cal = calendary().load(publicHolidays).load(team);

Every enabled collection’s events coexist, ordered by priority (higher first, like CSS z-index; event-level overrides the collection’s):

cal.getDay("2027-01-01").events.map(e => `${e.title} (${e.priority})`);
// → ["New Year (100)", "Team kickoff (50)"]

The 2026 kickoff is skipped — gone from the output, but getSkipped() lists it (only real occurrences; a stale exception key is ignored). Each entry carries the source event’s metadata, so you can render the ghost/struck-through chip from the same domain object — no side id → event lookup. A rescheduled occurrence carries movedFrom:

cal.getEventsInRange("2026-01-01", "2026-01-01").map(e => e.title); // → ["New Year"]
cal.getSkipped({ from: "2026-01-01", to: "2026-12-31" });
// → [{ date: "2026-01-01", sourceEventId: "kickoff", groupId: "team",
// title: "Team kickoff", reason: "exception", metadata: { owner: "ops" } }]
cal.requiredPlugins(); // → [] — the minimal plugin manifest for everything loaded
cal.setGroupEnabled("team", false); // hide a whole collection, like un-checking a calendar
cal.getDay("2027-01-01").events.map(e => e.title); // → ["New Year"]

toBundle() exports every group as its own collection — id / priority / color preserved — and load() expands the whole bundle in one call. So a single shareable .cdy carries several named, independently-styled collections instead of flattening to one.

const bundle = cal.toBundle({ version: "1.0.0" });
// → { version, collections: [{ id: "public-holidays", priority: 100, events: [...] },
// { id: "team", priority: 50, events: [...] }] }
const restored = calendary().load(bundle); // every collection back as its own group

Full reference: Collections & groups.