How to Generate Correct ICS Files From Your App (Best Practices + Templates)
A practical guide to producing ICS files that import cleanly in Google, Outlook, Apple Calendar, Teams, and iCal
If your app exports calendar events — for reminders, schedules, bookings, appointments, or syncing — you’ll eventually need to generate an ICS (iCalendar) file.
ICS looks simple, but generating a correct ICS that imports consistently across:
-
Google Calendar
-
Outlook / Office 365 / Exchange
-
Apple Calendar (iCal)
-
Teams
-
mobile calendar apps
-
legacy vendors
…is harder than it seems.
This guide walks you through:
-
minimal working ICS templates
-
common pitfalls when generating files
-
exporting recurring events correctly
-
time zone best practices
-
how to test your ICS generator
-
programmatically validating output
If you’re building ICS export functionality in a SaaS app, a booking engine, a scheduling tool, or a system that sends downloadable events, this is the definitive playbook.
1. Minimal Valid ICS Template (The Smallest File That Works Everywhere)
Every ICS file requires the following structure:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//YourApp//EN
BEGIN:VEVENT
UID:123456@example.com
DTSTAMP:20250310T120000Z
DTSTART:20250310T130000Z
DTEND:20250310T140000Z
SUMMARY:Example Event
END:VEVENT
END:VCALENDAR
Required fields:
Component
Required Fields
VCALENDAR
BEGIN:VCALENDAR, VERSION:2.0, PRODID, END:VCALENDAR
VEVENT
BEGIN:VEVENT, UID, DTSTART, DTEND* or DURATION, END:VEVENT
(*DTEND is strongly recommended for broad compatibility.)
Why this works everywhere
-
Correct structure
-
UTC times (Zulu
Z) -
Valid UID
-
Minimal fields but complete
This is your baseline template before adding complexity like time zones, recurrence, or descriptions.
2. Generating UIDs (Most Developers Get This Wrong)
UIDs must be:
-
unique
-
deterministic if possible
-
stable across updates
-
globally resolvable (not required to be an actual domain)
A good pattern:
UID:${eventId}@yourapp.com
Example:
UID:evt1234@myapp.com
Do not use random UUIDs on every export of the same event — otherwise sync tools treat each export as a new event.
3. Timestamp Rules (DTSTART, DTEND, DTSTAMP)
DTSTAMP: When the event was created (always UTC)
DTSTART/DTEND: When it happens (your choice of UTC or local time)
Best practice:
-
Use UTC for single events
-
Use local time (with TZID) for recurring events that must respect DST
Examples:
UTC Timestamp
DTSTART:20250310T140000Z
Local Time With TZID
DTSTART;TZID=America/New_York:20250310T090000
Incorrect:
DTSTART;TZID=EST:20250310T090000
(EST is not a valid IANA TZID.)
4. Generating Time Zones Correctly (TZID + VTIMEZONE)
If you export events with local times without a Z suffix, you MUST include TZID.
Valid:
DTSTART;TZID=Europe/London:20250310T090000
Invalid:
DTSTART;TZID=GMT:20250310T090000
DTSTART;TZID=EST:20250310T090000
Do you need a VTIMEZONE block?
It depends:
Client / Platform
Requires VTIMEZONE?
Google Calendar
❌ No
Outlook Web
❌ No
Outlook Desktop
✔️ Yes
Exchange
✔️ Yes
Apple Calendar
✔️ Usually
Recommended practice
If supporting Outlook Desktop or Exchange → include VTIMEZONE.
Example (New York):
BEGIN:VTIMEZONE
TZID:America/New_York
BEGIN:STANDARD
DTSTART:20241103T020000
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20240310T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
END:VTIMEZONE
But: generating this manually is error-prone — most developers rely on an upstream library or normalize time zones via a tool like CorrectICS.
5. Recurring Events: RRULE Best Practices
This is the most common area where ICS generation breaks.
Simple weekly recurrence:
RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR
Monthly event:
RRULE:FREQ=MONTHLY;BYMONTHDAY=15
End date:
RRULE:FREQ=DAILY;UNTIL=20250331T000000Z
Common mistakes
❌ Missing FREQ:
RRULE:BYDAY=MO
❌ Invalid UNTIL date:
RRULE:FREQ=DAILY;UNTIL=20250230T000000Z
❌ Infinite recurring events without COUNT or UNTIL
(some clients reject or slow down on import)
6. Line Folding (The Least Known ICS Requirement)
ICS lines should be no longer than ~75 bytes and must be folded using:
-
CRLF (
\r\n) -
continuation lines starting with a space
Broken:
DESCRIPTION:This is a very long description that some calendar apps will reject because it is too long to be on one line...
Correct:
DESCRIPTION:This is a very long description that some calendar apps will reject
because it is too long to be on one line...
Most developers forget this → causes Outlook import failures.
CorrectICS handles folding automatically.
7. Complete ICS Templates You Can Copy
Single Event (UTC)
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//MyApp//EN
BEGIN:VEVENT
UID:event-1234@myapp.com
DTSTAMP:20250201T120000Z
DTSTART:20250310T140000Z
DTEND:20250310T150000Z
SUMMARY:Sample Event
DESCRIPTION:This is an example event.
END:VEVENT
END:VCALENDAR
Local Time with Time Zone
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//MyApp//EN
BEGIN:VTIMEZONE
TZID:America/New_York
BEGIN:STANDARD
DTSTART:20241103T020000
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20240310T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:event-5678@myapp.com
DTSTAMP:20250201T120000Z
DTSTART;TZID=America/New_York:20250310T090000
DTEND;TZID=America/New_York:20250310T100000
SUMMARY:Local Time Event
END:VEVENT
END:VCALENDAR
Recurring Event Template
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//MyApp//EN
BEGIN:VEVENT
UID:class-4321@myapp.com
DTSTAMP:20250201T120000Z
DTSTART;TZID=America/Denver:20250310T090000
DTEND;TZID=America/Denver:20250310T100000
SUMMARY:Class
RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR
END:VEVENT
END:VCALENDAR
8. How to Test Your ICS Generator
1. Import into multiple clients
-
Google Calendar
-
Outlook Web
-
Outlook Desktop
-
Apple Calendar
2. Check time zone conversions
Ensure no shifts occur around DST transitions.
3. Validate with CorrectICS
curl -X POST https://api.correctics.com/v1/validate \
-H "Content-Type: text/calendar" \
--data-binary @event.ics
4. Autofix output before delivering to customers
curl -X POST https://api.correctics.com/v1/autofix \
-H "Content-Type: text/calendar" \
--data-binary @event.ics \
-o clean.ics
5. Include ICS validation in CI
(more examples in previous article)
9. Common Mistakes When Generating ICS Files
❌ Invalid or missing TZID
❌ Missing END:VEVENT / END:VCALENDAR
❌ Inline long lines without folding
❌ Missing UID
❌ Malformed RRULE
❌ Using Windows time zones
❌ Exporting invalid dates (Feb 30, etc.)
❌ Forgetting CRLF line endings
CorrectICS catches all of these.
10. Recommended Best Practices (TL;DR)
-
Use UTC for simple events
-
Use TZID + VTIMEZONE for local-time recurring events
-
Always include UID, DTSTAMP, VERSION, PRODID
-
Fold lines properly
-
Validate every ICS file before sending it
-
Provide downloadable ICS + add-to-calendar links
-
Use CorrectICS to clean, validate, and autofix your output
11. Conclusion: Good ICS Generation = Fewer Support Tickets
Exporting correct ICS files is essential for:
-
SaaS scheduling tools
-
booking applications
-
edtech platforms
-
CRM → calendar integrations
-
workplace shift scheduling
-
ICS feed publishers
A single faulty ICS export can result in:
-
wrong time zones
-
missing events
-
customer confusion
-
silent failures in Google/Outlook
Using the best practices above — and validating your output automatically — ensures your exports “just work” everywhere.
👉 Validate or autofix your ICS files instantly with CorrectICS
👉 Use the API in your ICS generator for guaranteed correctness