The ICS (iCalendar) format is a text-based standard for calendar events that every major calendar application supports: Google Calendar, Apple Calendar, Outlook, Thunderbird. When you click "Add to Calendar" on a website, you are downloading an .ics file. Understanding the format lets you programmatically generate calendar events for your applications.
The format
An ICS file is plain text with a specific structure:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Your App//EN
BEGIN:VEVENT
DTSTART:20260401T090000Z
DTEND:20260401T100000Z
SUMMARY:Team Standup
DESCRIPTION:Daily team sync meeting
LOCATION:Conference Room B
STATUS:CONFIRMED
UID:unique-id-12345@yourdomain.com
END:VEVENT
END:VCALENDAR
Each event is a VEVENT block inside a VCALENDAR container. The required fields are DTSTART, SUMMARY, and UID. Everything else is optional but useful.
Date-time formats
ICS supports several date-time formats:
UTC (recommended): End with Z
DTSTART:20260401T090000Z
Local time with timezone:
DTSTART;TZID=America/New_York:20260401T090000
All-day events: Date only, no time
DTSTART;VALUE=DATE:20260401
DTEND;VALUE=DATE:20260402
Note that all-day events use the day after as the end date. An all-day event on April 1st has DTEND of April 2nd. This is a common source of off-by-one errors.
Timezone handling is the trickiest part. If you use UTC times, the calendar application converts to the user's local timezone. If you use TZID, you need to include a VTIMEZONE component in the file that defines the timezone rules (including daylight saving transitions). For simplicity, use UTC whenever possible.
Recurring events
The RRULE property defines recurrence:
RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20261231T235959Z
This creates an event every Monday, Wednesday, and Friday until the end of 2026. Other options:
RRULE:FREQ=MONTHLY;BYMONTHDAY=15 # 15th of every month
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU # 2nd Sunday of March
RRULE:FREQ=DAILY;COUNT=10 # Daily for 10 occurrences
RRULE:FREQ=WEEKLY;INTERVAL=2 # Every other week
Alarms and reminders
Add reminders with VALARM:
BEGIN:VALARM
TRIGGER:-PT15M
ACTION:DISPLAY
DESCRIPTION:Meeting in 15 minutes
END:VALARM
TRIGGER:-PT15M means 15 minutes before the event start. -PT1H is one hour before. -P1D is one day before. You can include multiple VALARM blocks for multiple reminders.
Generating ICS files in code
function createICSEvent({ title, start, end, description, location }) {
const formatDate = (date) => {
return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
};
return [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//MyApp//EN',
'BEGIN:VEVENT',
`DTSTART:${formatDate(start)}`,
`DTEND:${formatDate(end)}`,
`SUMMARY:${title}`,
`DESCRIPTION:${description || ''}`,
`LOCATION:${location || ''}`,
`UID:${crypto.randomUUID()}@myapp.com`,
`DTSTAMP:${formatDate(new Date())}`,
'END:VEVENT',
'END:VCALENDAR'
].join('
');
}
The line endings must be (CRLF) per the spec. Some calendar apps are forgiving about this; others are not.
Common pitfalls
Line length. The spec limits lines to 75 octets. Longer lines must be folded: insert (CRLF followed by a space) to continue on the next line. Most calendar apps handle unfold lines, but spec compliance prevents edge cases.
Special characters. Commas, semicolons, and backslashes in property values must be escaped: \, \; \. Newlines in description fields use .
UID uniqueness. Each event must have a globally unique UID. If you send an updated .ics file with the same UID, calendar apps treat it as an update to the existing event rather than a new event. This is a feature for event updates but a bug if your UIDs collide accidentally.
I built an ICS calendar generator at zovo.one/free-tools/ics-calendar-generator that creates properly formatted .ics files with support for all-day events, recurring events, reminders, timezone handling, and multi-event calendars. Fill in the event details and download a file that works in every calendar application.
I'm Michael Lip. I build free developer tools at zovo.one. 500+ tools, all private, all free.
Top comments (0)