org-to-cal - Syncing Org Mode to macOS Calendar

Why?

I am greatly fond of Org Mode. Everything happens in Org: tasks, notes, scheduling, you name it.

But here is the thing. I still want my events to show up in macOS Calendar. Why? Well, Calendar.app has nice widgets. It syncs to my iPhone. It sends me notifications that actually work. And sometimes, I just want to glance at my day without firing up Emacs. Also: Why not?

So the question becomes:

how do I get my Org scheduled items into Calendar.app without copy-pasting like some kind of animal?

What about existing solutions?

org-caldav is the full-featured option – bidirectional sync via CalDAV. But iCloud authentication is painful, and it kept breaking on me. Life is too short.

org-mac-iCal syncs the wrong direction (Calendar → Org) and hasn’t been maintained since OS X 10.5.

Org’s built-in ox-icalendar just exports. You still need to automate it and get the file into Calendar somehow. That’s the gap I wanted to fill.

The Solution

With a lot of coding assistant, a lot of (reverse) engineering and a lot of code review so that I do not break my stuff…

Enter org-to-cal – an Emacs package I wrote to solve exactly this. It exports Org items tagged with :ical: to macOS Calendar. Automatically. On save.

The package lives here: https://gitlab.com/aimebertrand/org-to-cal

How It Works

The concept is simple:

  1. Tag your Org entries with :ical:
  2. Save the file
  3. The event appears in Calendar.app

Behind the scenes, it uses Org’s built-in ox-icalendar to generate a standard .ics file. Getting that file into Calendar is where it gets interesting.

Two Sync Methods

I implemented two different approaches. Pick your poison.

Server Method

A small HTTP server (Python, embedded in the package) serves the .ics file on localhost:8042. You subscribe to it in Calendar.app like any calendar subscription.

A LaunchAgent starts the server on reboot of the mac.

Direct Import Method

A Swift executable talks directly to EventKit (macOS’s calendar API). A LaunchAgent watches the .ics file and imports changes immediately.

Pros: Instant updates. No polling. Cons: Slightly more complex. Does not delete events (only creates/updates).

Or both

You can run both simultaneously if you want redundancy. I do.

How it works

Well i do not wanna bore you with all the details. The README.org is quite good and extensive.

Bonus: Cross-Machine Support

The direct import method has a neat trick. It stores event UIDs in a uid_map.plist file and embeds them in the event notes as [org-uid:xxx]. This means if you sync via iCloud and the local UID mapping is lost, it can recover by searching the notes field.

Granted, this is an edge case. But when it works, it feels like magic.

Caveats

Be advised:

This is one-way sync. Org → Calendar. If you edit events in Calendar.app, those changes will not come back to Org.

Also, the direct import method cannot delete events. If you remove the :ical: tag from an Org entry, the event stays in Calendar. You will have to delete it manually. ¯\_(ツ)_/¯

Conclusion

org-to-cal scratches a very specific itch of mine. I wanted my Org schedule in Calendar.app without thinking about it. Now it just happens.

The code is MIT licensed. Contributions welcome.

Resources