Collections & groups
As a calendar grows, keeping every event in one flat list gets hard to reason about. The pattern we recommend for anything non-trivial: split events into named groups and compose the calendar from them. A group is an independent, toggleable, separately-exportable set — so you build from parts and maintain each part on its own.
You get a group two ways:
cal.addGroup({ id, events })— declare one inline.cal.load(collection)— load a portable collection (the plain-JSON.cdythe builder or the Compose tool produce). Load several to compose one calendar from many.
Both merge into the same calendar; every computed event carries the groupId it
came from.
Group your events — addGroup
Section titled “Group your events — addGroup”import { calendary } from "calendaryjs";import { every, date } from "calendaryjs/builder";
const cal = calendary();
cal.addGroup({ id: "holidays", events: [every("year").on(date(12, 25)).title("Winter break")],});
cal.addGroup({ id: "team", events: [every("week").on("monday").title("Standup")],});
cal.getEventsInRange("2025-01-01", "2025-12-31"); // both groups, merged + orderedLoad a portable collection — load
Section titled “Load a portable collection — load”A collection is a plain object (or JSON string) — { collection, plugins?, events } — the exact shape toCollection() and Compose emit.
load() first checks that every plugin the collection declares is registered
(one clear error listing any that aren’t), then adds the events as a group whose
id is the collection’s name:
import { calendary } from "calendaryjs";import { lunar } from "calendaryjs-plugin-lunar";
const publicHolidays = { collection: "public-holidays", plugins: ["calendaryjs-plugin-lunar"], events: [ /* … plain event configs … */ ],};
const cal = calendary().use(lunar()); // install the plugins the collection declarescal.load(publicHolidays); // → group "public-holidays"load() never fetches or installs anything — you register plugins with
use(), the collection only declares which it needs.
Compose from multiple collections — the recommended shape
Section titled “Compose from multiple collections — the recommended shape”Author one collection per concern, then load() each into its own group.
load() returns the instance, so it chains:
const cal = calendary() .use(lunar()) .load(publicHolidays) // → group "public-holidays" .load(companyCalendar) // → group "company" .load(personalCalendar); // → group "personal"
cal.getDay("2025-12-25").events; // every group's events for the day, merged + orderedGive each collection a distinct collection name — that name becomes its group
id, so distinct names keep the groups from colliding.
Why this beats one flat list:
- Maintainable — edit
public-holidayswithout touchingcompanyorpersonal. - Reusable — ship a collection as a
.cdyfile or an npm package; anyoneload()s it. No event code to copy. - Toggleable — show or hide a whole collection at runtime (below).
- Attributable — every event knows its
groupId, so you can colour, filter, or export by collection.
One file, several collections — a bundle
Section titled “One file, several collections — a bundle”You can also ship them as a single file. A .cdy may carry several named
collections in a { collections: [...] } bundle; load() expands the whole
bundle in one call, each collection keeping its own id / priority / color:
const bundle = { collections: [ { id: "public-holidays", priority: 100, events: [...] }, { id: "personal", priority: 50, color: "teal", events: [...] }, ],};
calendary().load(bundle); // → groups "public-holidays" and "personal"A single Collection and a bundle both flow through the same load() — pass
whichever you have. Produce a bundle from a live instance with
toBundle().
Work with a group
Section titled “Work with a group”cal.getGroup("company"); // inspect one groupcal.setGroupEnabled("personal", false); // hide a whole collection — its events leave the outputcal.setGroupEnabled("personal", true); // show it againcal.removeGroup("personal"); // drop it entirelyBecause every event carries its origin, you can also filter in queries:
cal.search().source("public-holidays").year(2025).getEvents(); // one collection's eventsExport a group
Section titled “Export a group”toCollection() is the inverse of load() — it produces a portable collection
(pass it to JSON.stringify for a .cdy). Export one group or the whole
calendar:
cal.toCollection({ group: "company", name: "company" }); // just this collectioncal.toCollection({ name: "everything" }); // every group's events, flattenedTo keep every group separate in one file — id / priority / color preserved — export a bundle instead of flattening:
cal.toBundle({ version: "1.0.0" }); // { collections: [ …one per group… ] }No instance handy? buildCollection(configs, { name, plugins, schema }) assembles a
.cdy from plain configs statelessly — it derives the plugin manifest from the
plugins you pass and can stamp $schema:
import { buildCollection } from "calendaryjs";
const cdy = buildCollection(configs, { name: "family", plugins: [lunar()], schema: "https://calendaryjs.dev/schema/cdy.json",});When collections overlap on a day
Section titled “When collections overlap on a day”This is the question that matters most once you compose from several sources: what happens when one day has five events, each from a different collection?
calendaryjs keeps all of them — it never silently picks a winner or hides
one. Every enabled group’s events for a day are merged into one list and
ordered by priority (higher first, exactly like CSS z-index; default
0; ties keep generation order). The engine is a mechanism, not a policy: you
decide precedence.
const cal = calendary() .load(publicHolidays) // its events carry priority: 100 .load(company) // priority: 50 .load(personal); // priority: 0 (default)
const day = cal.getDay("2025-12-25");day.events.length; // 5 — nothing droppedday.events[0].title; // the highest-priority event = the day's "primary"You have three levers, none of which delete anything:
- Order across collections. Author a collection’s events with a higher
prioritytier so that collection sits on top on busy days — see Precedence. Tag events with asourceto filter or reason about them in bulk. - Hide a whole collection.
cal.setGroupEnabled(id, false)takes its events out of the output entirely — like un-checking a calendar in Google or Apple Calendar. - Let one date yield. A single event can pre-declare that it steps aside on
a known-busy date — drop or reschedule that date only — via
onConflict,exceptions, oroverrideDates. These are per-event and author-declared; they act on the one event, not on whatever else shares the day. See customizing a single occurrence.
So precedence between collections is just priority; visibility is
setGroupEnabled; and a single event yielding on a specific date is
onConflict / exceptions. Nothing is ever dropped without you asking.
Authoring collections
Section titled “Authoring collections”Write a collection by hand as JSON, generate it from the builder
with toCollection(), or compose one visually in Compose and export
the .cdy. However you make it, load() reads it back the same way.