guides

The complete ICS file guide

Everything you need to know about ICS files โ€” what they are, how the iCalendar spec works, how to generate them correctly, and how to debug the common edge cases that break Outlook, Apple Calendar, and timezone handling.

By The SpreadEvent TeamยทApril 27, 2026ยท16 min read
The complete ICS file guide cover

ICS files are the universal calendar format. Send an ICS file to any modern device and it opens in the user's calendar app โ€” Apple Calendar, Outlook, Google Calendar, Mozilla Thunderbird, the Linux GNOME Calendar, an Android tablet, all of them.

Behind that universality is a 200-page IETF specification (RFC 5545) full of quirks. Most ICS files in the wild break in at least one calendar client because of those quirks.

This is the complete guide. By the end you'll know how to generate ICS files correctly, what the common bugs are, why timezones are the hardest part, and when to skip all of this and use a tool.

Key takeaways

  • โœ“ICS files implement the iCalendar standard (RFC 5545). One file can describe events, recurring events, todos, and free/busy status โ€” but most use cases only need VEVENT.
  • โœ“The most common bug is timezone handling. There are three ways to specify a time and only two work reliably. We cover when to use which.
  • โœ“Line endings must be CRLF (\r\n), not LF. Lines longer than 75 octets must be folded. Get either wrong and Outlook silently fails to import.
  • โœ“Apple Calendar, Outlook desktop, and Google Calendar each have different parsing quirks. We list the ones that bite in production.

What is an ICS file?

.ics is the file extension for an iCalendar document โ€” a plain-text format defined by the IETF in RFC 5545 (current version, replacing the older RFC 2445 from 1998).

When a user clicks an ICS file link, their operating system looks at the MIME type (text/calendar) and the file extension, then opens the file with the default calendar application. Apple's macOS opens Calendar.app, iOS opens Calendar, Windows 10/11 opens Outlook (or the built-in Calendar app), Android typically opens Google Calendar. Linux varies by desktop environment.

The iCalendar standard supports four object types:

  • VEVENT โ€” a single event ("Q4 webinar on June 15 at 6pm")
  • VTODO โ€” a task with optional due date ("Call client, due Friday")
  • VJOURNAL โ€” a dated note (most clients ignore these)
  • VFREEBUSY โ€” availability status ("I'm busy from 2โ€“4pm")

For 99% of use cases โ€” webinars, meetings, classes, podcast releases โ€” you only need VEVENT. The rest of this guide focuses there.

The simplest valid ICS file

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Your Company//Your Product//EN
BEGIN:VEVENT
UID:webinar-q4-2026@yourdomain.com
DTSTAMP:20260427T120000Z
DTSTART:20260615T180000Z
DTEND:20260615T190000Z
SUMMARY:Q4 Product Launch Webinar
DESCRIPTION:Live walkthrough of the new dashboard.
LOCATION:https://zoom.us/j/123456789
END:VEVENT
END:VCALENDAR

That's it. Save as event.ics, serve it with Content-Type: text/calendar, and clicking the link in any modern email client or browser will offer to add the event.

Every line above is required for a valid file. Drop any of them and you'll see one of three failure modes: silent failure (Outlook), error message (Apple Calendar), or "import successful but the event is missing details" (Google Calendar โ€” they're forgiving).

โœ“

Validate your output

The free iCalendar.org validator catches 90% of common bugs. Run every ICS template through it before shipping.

The required fields, explained

BEGIN:VCALENDAR / END:VCALENDAR

The outer wrapper. Every ICS file must start and end with these. You can have multiple VEVENT blocks inside โ€” one calendar file can describe a whole conference's worth of events.

VERSION:2.0

The iCalendar spec version. Always 2.0. Anything else and parsers will refuse the file.

PRODID

A free-text identifier for the software that generated the file. The spec recommends -//Your Company//Your Product//EN format but doesn't strictly require it. Useful for debugging โ€” when a user sends you a broken ICS file, the PRODID tells you which tool generated it.

BEGIN:VEVENT / END:VEVENT

The event wrapper. Each event has its own block. Required fields inside:

  • UID โ€” globally unique identifier. Must be stable across regenerations of the same event (see below). The recommended format is <some-id>@<your-domain>.
  • DTSTAMP โ€” when the ICS file was created. Always UTC, format YYYYMMDDTHHMMSSZ.
  • DTSTART โ€” when the event starts. UTC or with timezone (see timezone section below).
  • SUMMARY โ€” the event title. Don't put HTML in here; many clients render it literally.

Optional but commonly used

  • DTEND โ€” when the event ends. If absent, calendars assume an "all-day" or "instant" event.
  • DURATION โ€” alternative to DTEND. Use either, never both.
  • DESCRIPTION โ€” the event description. Plain text only. Newlines escaped as \n.
  • LOCATION โ€” physical address, URL (Zoom link), or phone number.
  • URL โ€” separate "more info" URL (your event page).
  • ORGANIZER โ€” who's hosting. Format ORGANIZER;CN=Name:mailto:email@example.com.
  • ATTENDEE โ€” who's invited (sends an actual invitation if used in an email-side ICS attachment).

The three rules that break Outlook

Three formatting rules in RFC 5545 catch developers out and silently break ICS files when imported into Outlook desktop. Get all three right and your files will import everywhere.

Rule 1: CRLF line endings

The spec requires \r\n (carriage return + line feed) between every line. Not \n alone. Not \r alone.

// Wrong โ€” Outlook silently fails to import
const ics = `BEGIN:VCALENDAR\nVERSION:2.0\n...`;

// Right โ€” works everywhere
const ics = `BEGIN:VCALENDAR\r\nVERSION:2.0\r\n...`;

If you write the file in a text editor, make sure the editor is using CRLF line endings. VS Code's status bar shows this in the bottom-right.

Rule 2: Line folding

Lines longer than 75 octets (bytes) must be "folded" โ€” split across multiple lines, with each continuation line starting with a single space or tab.

DESCRIPTION:This is a description that's longer than seventy-five octets and 
 needs to be folded across multiple lines or Outlook will refuse it

The space at the start of the second line is critical. It tells the parser "this is a continuation of the previous line, not a new property."

If your descriptions are under 75 characters, you can ignore this. If they're longer, fold them โ€” or better, let your generator library handle it.

Rule 3: Escape special characters

In SUMMARY, DESCRIPTION, and LOCATION values:

  • \ becomes \\
  • ; becomes \;
  • , becomes \,
  • newline becomes \n (literal backslash-n, not a real newline)

So this:

Q4 Webinar; Q&A at end. Discount: 20%

Encodes as:

SUMMARY:Q4 Webinar\; Q&A at end. Discount: 20%

Forget to escape the semicolon and Outlook truncates the title at the unescaped character.

Don't write ICS files by hand

SpreadEvent generates spec-compliant ICS automatically โ€” line folding, escaping, timezone handling all handled. Free for 3 events, no credit card.

Try free

Timezones: the part everyone gets wrong

This is the most common source of ICS bugs. There are three ways to specify the time of an event, and only two of them work reliably.

DTSTART:20260615T180000Z
DTEND:20260615T190000Z

The Z at the end means "Zulu / UTC time." Every calendar app converts this to the user's local timezone. June 15, 18:00 UTC becomes 11am for a Pacific user, 8pm for a Central European user, 3am next day for a Sydney user.

Use this for: webinars, online meetings, livestreams, podcast releases โ€” anything where attendees are global and the event happens at a single global moment.

Option 2: Floating time (no timezone)

DTSTART:20260615T180000
DTEND:20260615T190000

No Z, no timezone reference. Calendars interpret this as "18:00 in the viewer's local timezone." That means a London user sees the event at 6pm London time, and a New York user sees it at 6pm New York time โ€” different absolute moments.

Use this for: "Take your medication at 8am" or "Lunch break at noon" โ€” events that happen at the same wall-clock time regardless of where the user is.

Don't use this for events with global attendees. This is the bug we see most often: somebody puts a webinar URL in a floating-time event, then has to apologise to half the audience for the wrong start time.

Option 3: Specific timezone (the powerful but tricky one)

BEGIN:VTIMEZONE
TZID:America/New_York
BEGIN:DAYLIGHT
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
END:VTIMEZONE

BEGIN:VEVENT
DTSTART;TZID=America/New_York:20260615T140000
DTEND;TZID=America/New_York:20260615T150000
END:VEVENT

The event starts at 14:00 New York time. The VTIMEZONE block tells the parser exactly how DST works in New York so the event still resolves correctly across DST boundaries.

Use this for: in-person events with a definite physical location ("Meeting at our NYC office at 2pm Eastern"). The advantage over UTC is that if a user moves between timezones, their calendar still shows the event in NYC time, which is what they want.

Caveat: writing a correct VTIMEZONE block is hard. Use a library โ€” almost every language has one (ical.js for JavaScript, icalendar for Python, Net::ICal for Perl, etc).

โš 

DST changes will catch you

If you store events in a non-UTC timezone without a proper VTIMEZONE block, your event times can shift by an hour twice a year as DST kicks in or out. We've seen this happen with high-traffic webinars where a March-October planning slack meant the May test passed but the November webinar started at the wrong hour.

Recurring events with RRULE

If your event repeats, use the RRULE property:

BEGIN:VEVENT
UID:weekly-office-hours@yourdomain.com
DTSTAMP:20260427T120000Z
DTSTART:20260601T160000Z
DTEND:20260601T170000Z
SUMMARY:Weekly Office Hours
RRULE:FREQ=WEEKLY;BYDAY=MO;UNTIL=20261231T235959Z
END:VEVENT

That's "every Monday at 16:00 UTC, repeating until end of 2026."

RRULE syntax basics:

  • FREQ=DAILY|WEEKLY|MONTHLY|YEARLY โ€” required
  • INTERVAL=N โ€” every N units (e.g., INTERVAL=2;FREQ=WEEKLY is biweekly)
  • BYDAY=MO,TU,WE,TH,FR โ€” specific weekdays
  • BYMONTHDAY=15 โ€” specific day of the month
  • COUNT=N โ€” stop after N occurrences
  • UNTIL=YYYYMMDDTHHMMSSZ โ€” stop on a specific date

The full grammar is complex enough that there are "RRULE testing tools" you can throw an expression at to see what dates it actually generates.

Recurring event quirks across clients

  • Outlook desktop is the strictest. Recurring events with complex RRULEs often fail to import. If you're targeting Outlook desktop, stick to weekly/monthly with simple BYDAY clauses.
  • Apple Calendar handles complex RRULEs well but doesn't show "until" dates in the UI.
  • Google Calendar is forgiving. Almost any valid RRULE works.

Cancelling and updating events

If you've already sent an ICS file and need to update or cancel the event, you don't send a fresh ICS with a new UID. You send an ICS with the same UID and bump the SEQUENCE property:

BEGIN:VEVENT
UID:webinar-q4-2026@yourdomain.com
SEQUENCE:1
DTSTAMP:20260501T120000Z
DTSTART:20260616T180000Z   <-- new date
DTEND:20260616T190000Z
SUMMARY:Q4 Product Launch Webinar (rescheduled)
END:VEVENT

To cancel the event entirely, set STATUS:CANCELLED and METHOD:CANCEL:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Your Company//Your Product//EN
METHOD:CANCEL
BEGIN:VEVENT
UID:webinar-q4-2026@yourdomain.com
SEQUENCE:2
DTSTAMP:20260510T120000Z
STATUS:CANCELLED
SUMMARY:Q4 Product Launch Webinar
END:VEVENT
END:VCALENDAR

When the user opens this in their email client, it removes the previously-imported event from their calendar.

The UID matching is what makes this work. If you change the UID, calendars treat the new ICS as a separate event and you end up with two of the same.

Serving ICS files correctly

How you deliver the file matters as much as what's in it.

Content type

Always serve with Content-Type: text/calendar (not application/octet-stream, not text/plain). Email clients sniff this header to decide how to render the attachment.

Charset

Specify UTF-8 explicitly: Content-Type: text/calendar; charset=utf-8. Without this, some Outlook versions assume Windows-1252 and fail on non-ASCII characters.

File name

Set Content-Disposition: attachment; filename="event.ics" (or inline; filename="event.ics"). The .ics extension is what tells the OS which app to open.

Cache headers

If your ICS file represents a single fixed event, cache it aggressively (Cache-Control: public, max-age=86400). If it's dynamic (e.g., a personalised RSVP confirmation), set Cache-Control: private, no-store.

Don't gzip

Some clients (older Outlook versions especially) don't decompress gzipped ICS files. Disable compression on the response.

Common bugs and how to debug them

"The event imports but the times are wrong"

Almost always a timezone bug. Check:

  1. Is your DTSTART in UTC (with Z) or in a specific timezone (with TZID)?
  2. If it's "floating" (no Z, no TZID), is that what you want? (Probably not for global events.)
  3. Does your test span a DST boundary? Test events in March, June, September, and November to catch all four DST transitions.

"The event doesn't import at all in Outlook"

Outlook is the strictest parser. Likely causes:

  1. Wrong line endings โ€” make sure CRLF.
  2. Unfolded long lines.
  3. Unescaped ; or , in SUMMARY/DESCRIPTION/LOCATION.
  4. Missing DTSTAMP.
  5. Recurring event with too-complex RRULE.

"The event imports but with no description"

Likely an escaping bug. Newlines in descriptions must be \n (literal backslash-n in the file), not actual newline characters. If you copy-paste from a text editor, the editor may convert \n to a real newline.

"I see the event twice in my calendar"

You're regenerating the UID on every download. Make UID deterministic from the event's identity (e.g., a hash of the event ID + your domain).

"The Apple Calendar app shows the event with no location"

The LOCATION field has a length limit in Apple Calendar (~256 chars). Long Zoom join URLs sometimes hit it. Move the URL to URL: and put a shorter description in LOCATION.

When to skip all this

You can write ICS files by hand. We just walked through it. But there's a reason every event-marketing tool, every ticketing platform, every meeting-scheduler service generates ICS internally instead of asking the user to write it: it's tedious, the bugs are subtle, and the quality bar is "import correctly in five different calendar clients across three operating systems."

If you have ten people on your team and one of them is a full-time engineer, build it. If you're a marketer or founder shipping a product, use a tool.

SpreadEvent is one of those tools. Free plan covers 3 events with all the calendar provider URLs and ICS generation handled correctly. The Pro plan ($27/mo yearly) lifts the caps and adds API + webhook access if you want to embed the ICS generation into your own workflow.

Or pick from the seven AddEvent alternatives we compared โ€” every one of them produces ICS files that pass validation.

FAQ

Yes. Put multiple VEVENT blocks inside a single VCALENDAR wrapper. Each event needs its own unique UID. This is how conferences with dozens of sessions distribute a single 'subscribe to my schedule' file.

Further reading

Summary

ICS files are simple in spirit and complex in detail. The minimal valid file is 11 lines. The complete spec covers timezones, recurrence, exceptions, attendee management, freebusy, journals, todos, and alarms.

For a single event with a global audience: use UTC times, a stable UID, escape your special characters, validate the output, and serve with Content-Type: text/calendar.

For everything else โ€” recurring meetings, multi-timezone in-person events, RSVP tracking, calendar subscriptions โ€” pick a library or a tool. Hand-rolling ICS at scale is a job for a dedicated engineer, not a side project.

If you'd rather skip the spec entirely, start free at SpreadEvent. We handle every edge case in this article and ship the calendar buttons that wrap around the file.


Newsletter

New posts. No spam.

Calendar marketing tips, comparisons, and behind-the-scenes from SpreadEvent. ~1 email per month.

Keep reading