ical-generator
A zero-dependency TypeScript library for generating RFC 5545-compliant iCal calendars and subscription feeds.
Repository Health
Technical Analysis
ical-generator is a TypeScript library for building valid iCalendar (RFC 5545) data — the format behind calendar invites, subscription feeds, and .ics attachments. It provides a fluent, chainable API for constructing calendars, events, alarms, attendees, and categories, then serializing them to a spec-compliant .ics string.
The library is date-library agnostic: it accepts native Date objects as well as Day.js, Luxon, moment.js, moment-timezone, date-fns TZDate, and Temporal values, converting each internally to the correct UTC or timezone-qualified iCal format. Recurring events, VALARM reminders, VTIMEZONE generation, and Outlook-compatible REQUEST/REPLY methods are all supported out of the box, backed by a test suite that enforces 98% statement coverage on every change.
What You Get
- A chainable, object-oriented API (ICalCalendar → ICalEvent → ICalAlarm/ICalAttendee/ICalCategory) instead of raw iCal string templating
- Support for six date/time libraries (native Date, Day.js, Luxon, moment, moment-timezone, Temporal, date-fns TZDate) with automatic conversion
- Built-in RFC 5545 line-folding, escaping, and VALARM/VTIMEZONE generation handled for you
- Dual ESM/CJS builds with full TypeScript types, plus a JSR publish target
Common Use Cases
- Generating subscribeable .ics calendar feeds for a booking or events platform
- Sending Outlook/Google-compatible meeting invitations from a Node.js backend
- Attaching a calendar file to a transactional email or webhook payload
- Building recurring-event schedules (RRULE-based) for SaaS scheduling tools
Under The Hood
Architecture
ICalCalendar acts as the root aggregate, holding an internal data object (typed via ICalCalendarInternalData) plus an array of ICalEvent instances; each ICalEvent in turn owns its own alarms, attendees, and categories through the same private-data-plus-chainable-setter pattern seen across calendar.ts, event.ts, alarm.ts, attendee.ts, and category.ts. The public entry point (src/index.ts) is a thin facade that just re-exports these classes and their type definitions, with the ical() factory function delegating straight to new ICalCalendar(data). Serialization flows top-down: calendar.toString() walks its events array and calls each event’s own toString(), which recurses into its alarms/attendees, with shared helpers in tools.ts (foldLines, escape, formatDate) handling RFC 5545’s line-folding and escaping rules consistently across every component. Because every class exposes the same dual constructor-object-or-chained-setter interface, a change to any internal data shape cascades predictably into both the JSON export and the string serializer for that component, keeping the blast radius contained to one file per concept.
Tech Stack Written entirely in TypeScript, built with tsdown into dual ESM (.mjs) and CJS (.cjs) bundles plus .d.mts/.d.cts type declarations, and published to both npm and JSR. The library declares zero required runtime dependencies — date-library support (Day.js, Luxon, moment, moment-timezone, @date-fns/tz, Temporal, rrule, @touch4it/ical-timezones) is wired in entirely through optional peerDependencies, so consumers only pull in the date library they already use. Testing runs on Mocha with tsx for native TypeScript execution and c8/nyc for coverage; linting combines ESLint’s flat config, typescript-eslint, and eslint-plugin-perfectionist with Prettier, enforced pre-commit via Husky and in CI via GitHub Actions, with releases automated through semantic-release.
Code Quality The test/ directory mirrors src/ file-for-file (alarm.ts, attendee.ts, calendar.ts, category.ts, event.ts, tools.ts, types.ts, index.ts), plus dedicated cases.ts and issues.ts suites for cross-cutting scenarios and regression coverage. The nyc config enforces a hard coverage gate — 98% statements/lines, 85% functions, 80% branches, checked per file — so the suite can’t quietly regress. Every internal data shape and public method is fully typed, with overloaded signatures used deliberately to give getter and setter calls on the same method distinct, precise return types. Combined with the ESLint/Prettier/Husky/CI chain, this is a strongly enforced, typed, and tested codebase.
API Design
The dual configuration style — pass an options object to the constructor, or build the same result through chained setters — is documented consistently across every class via runnable JSDoc examples, so ical({name: 'x'}) and ical().name('x') are guaranteed equivalent rather than two loosely related paths. Accepting native Date alongside Day.js, Luxon, moment, moment-timezone, TZDate, and Temporal values without requiring consumers to normalize dates themselves removes a common integration tax for teams standardized on a different date library. Getting from zero to a working .ics file takes one line (ical({name}).createEvent({...}).toString()), and the example files spanning Express, Koa, Next.js, Deno, and micro demonstrate real integration patterns rather than toy snippets, meaningfully lowering the ramp-up cost for a new consumer.
Used by 3 apps in this directory
Budibase
Low Code Platforms · No Code Platforms
Build AI agents, automations, and internal apps on a single open-source platform with full self-hosting control.
Manage
Project Management · Collaboration
Self-hosted, open-source project management with tasks, calendars, docs, and real-time team collaboration.
twenty
CRM
The open-source CRM you build, ship, and version like the rest of your stack — with customizable objects, AI agents, and a TypeScript SDK.