ical-generator turns JavaScript objects into valid iCal feeds
ical-generator is a small piece of code which generates ical calendar files
At a glance
- What is it?
- ical-generator is a small TypeScript library that builds RFC style calendar files with a fluent API, and its sharpest edge is timezone handling, where a calendar level VTIMEZONE entry has to be generated by a separate package.
- Who is it for?
- ical-generator suits a Node or TypeScript service that needs to publish a subscriptionable calendar feed, and it suits teams that already work with Luxon, Day.js or Temporal and want their date objects accepted directly.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A calendar object is built, then serialised to text
ical-generator is described as a small but fine library for creating valid iCal calendars, for example to generate subscriptionable calendar feeds. The shape of the API is a calendar factory that returns an object with methods to attach a method, create events, set a timezone, and finally serialise. The quick start in the README shows the whole flow: create a calendar with a name, set the calendar method, create an event with start, end, summary, description, location and url, and then serve calendar.toString() over HTTP with the right content type.
import ical, { ICalCalendarMethod } from 'ical-generator';
import http from 'node:http';
const calendar = ical({ name: 'my first iCal' });
// A method is required for outlook to display event as an invitation
calendar.method(ICalCalendarMethod.REQUEST);The comment about Outlook is a practical detail rather than decoration. The calendar method is what tells a client how to treat the file, and for an invitation style feed the request method is required, otherwise Outlook will show the event as a plain entry rather than an invitation.
The HTTP part of the quick start is the part you should copy
The serving half of the example is small and easy to get wrong, so it is worth reading closely.
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/calendar; charset=utf-8',
'Content-Disposition': 'attachment; filename="calendar.ics"',
});
res.end(calendar.toString());
}).listen(3000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:3000/');
});Two headers carry the meaning. The content type has to be text/calendar with a UTF-8 charset, or a subscribing client will not parse the body, and the content disposition with an attachment filename is what makes a browser treat the response as a downloadable calendar. Note that the example binds to 127.0.0.1, which is right for a local test and wrong for a feed that clients are meant to fetch, so if you lift this code into production you have to change the bind address and put something in front of it. The examples directory in the repository goes further, with entries for Express, Koa, Micro, Next.js 13, Deno, Runkit and a plain http server, so the integration shape for a framework you already use is likely already written.
Timezone output is the design decision that bites
The README recommends using UTC time as far as possible, and states that ical-generator outputs all time information as UTC as long as no time zone is defined. That default is safe for absolute instants and wrong for local wall clock meetings, because a meeting that happens at 09:00 in Berlin becomes a 09:00 UTC event, which is two hours off in the reader's calendar. If a time zone is set, the library assumes the given time matches that time zone, and to make that work the calendar also needs a VTIMEZONE entry, which the library does not generate itself. The README says to use a VTimezone generator function, which produces a VTimezone entry and returns it, and names two packages that provide one, timezones-ical-library and @touch4it/ical-timezones.
import { ICalCalendar } from 'ical-generator';
import { getVtimezoneComponent } from '@touch4it/ical-timezones';
const cal = new ICalCalendar();
cal.timezone({
name: 'Europe/London',
generator: getVtimezoneComponent,
});
cal.createEvent({
start: new Date(),
timezone: 'Europe/London',
});So a correct calendar is a two package operation: one to build the events, one to supply the timezone definition. The README also notes that a moment-timezone object or a Luxon setZone call is handled according to the time zone set on the calendar or the event.
It accepts whatever date library you already use
The date input story is the friendliest part of the library. The README lists support for the native Date, Day.js, Luxon's DateTime, the older moment.js, moment-timezone, TZDate from date-fns, and Temporal, and you can also pass a string, which is handed to JavaScript's Date internally. That matters because a date library is a decision an application makes once and then lives with for years, and a calendar library that forces its own would be a nuisance. The cost of that flexibility is that the timezone story is delegated to those libraries, and a native Date carries no zone, so a native Date event without an explicit timezone falls back to the UTC output path described above. The examples directory has a dedicated example-temporal.ts and example-string.ts, which is a reasonable way to see which input style behaves the way you expect.
The API surface is narrower than the iCalendar format
The API reference in the README lists an index page and a small set of classes: ICalCalendar, ICalEvent, and under the event, ICalAlarm, ICalAttendee and ICalCategory. That is the whole documented surface, and it is a good indicator of the scope. Recurrence rules are supported, with rrule in the devDependencies and a recurrence example in the examples directory, and there are examples for repeating events as well. But there is no documented coverage for the more exotic corners of the specification, no validation class, no recurrence exception handling beyond the examples, and no documented way to sign or encrypt a feed. For a team publishing shift rosters, maintenance windows or a booking system, that is enough. For a team that wants a full calendar client, a subscription service that must handle EXDATE, RDATE and time zone registries correctly, this library is a starting point rather than a destination.
Testing, releases and a documented version history
The project has a proper engineering setup around a small library. The README documents two commands for the test suite.
npm test
npm run coverageThe repository carries a .mocharc.yml, an .nycrc for coverage, a .husky directory for git hooks, an eslint.config.js, a .prettierrc and a typedoc configuration, and the package.json lists mocha, nyc, c8, prettier and eslint among the development dependencies, along with dayjs, luxon, moment, moment-timezone, rrule, temporal-polyfill and tsx for testing across the supported date types. Releases are automated with semantic-release, including a GitHub Pages plugin, a JSR plugin, a changelog plugin and a license plugin, and the repository ships a jsr.json, so the package is published to more than one registry. The FAQ points at the CHANGELOG.md for the current line and gives a separate link for the 1.x.x and older changelog, which is a useful signal of how far the API has moved.
The one place the documentation is thin
Two gaps are worth naming. The first is validation. Nothing in the README describes a method to check that a generated calendar is valid, and the claim of validity rests on the library's own test suite and on the fact that clients accept the output. If your calendar is going to be consumed by Outlook, Apple Calendar and a dozen mobile clients with different levels of strictness, the practical test is to subscribe with each of them, not to run a validator, and the README does not tell you how to reproduce that. The second gap is error handling. The createEvent call in the quick start takes a plain object, and the API reference pages are where you would look for what happens when a date is missing, an end precedes a start, or a recurrence rule is malformed. The README itself does not address it, so if your feed is generated from user input you will need to read the reference or the source to find out whether invalid input throws or produces a broken file that fails silently in the client.
Editorial conclusion
ical-generator suits a Node or TypeScript service that needs to publish a subscriptionable calendar feed, and it suits teams that already work with Luxon, Day.js or Temporal and want their date objects accepted directly. It is the wrong tool for a project that needs the full breadth of the iCalendar specification, since the API reference covers a calendar, an event and an alarm, an attendee and a category, and no more, and it is the wrong tool when nobody wants to think about timezones, because the VTIMEZONE generation is pushed onto a separate package and the naive path produces UTC output that shifts the wall clock time of every event. Before adopting it, decide whether your events are floating local times or absolute instants, generate the VTIMEZONE entries if you need them, and read the CHANGELOG for the 1.x line separately, since older versions are documented at a different link. The last push was on 2026-10-14 and the current release line is v11.1.2 with a v11.1.3 develop tag.
Frequently asked questions
How do I install ical-generator?
Install it from npm with npm install ical-generator. The library is written in TypeScript and published to npm, and the repository also ships a jsr.json for JSR.
Which date libraries can I pass to ical-generator?
The README lists native Date, Day.js, Luxon DateTime, moment.js, moment-timezone, TZDate and Temporal, and you can also pass a string, which is passed to JavaScript's Date internally.
How do I handle timezones in ical-generator?
Without a time zone the library outputs all times as UTC. With a time zone set it assumes the given time matches that zone, and the README says to also use a VTimezone generator such as @touch4it/ical-timezones or timezones-ical-library to produce the VTIMEZONE entry.
What content type should an ical-generator feed be served with?
The quick start uses the content type text/calendar with a charset of utf-8, together with a content disposition header naming the file calendar.ics.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sebbo2002-ical-generator)Was this useful?