Notion2Ical: Turning Notion Tasks Into a Calendar Feed
How a personal Notion companion service became a .NET library that exposes open tasks as an iCalendar feed for common calendar clients.

In 2020, a colleague introduced me to Notion.
I remember being immediately struck by the shape of it. It was not only a notes app, and it was not only a database. It let me mix pages, tables, structured fields, relations, and free-form writing in the same place. I could model pieces of my life as small databases, then still keep the human context around them.
That was exactly the kind of tool I tend to fall into quickly.
I started moving structured information into Notion almost immediately. Notes, lists, references, planning material, small personal systems, and eventually my task manager.
The task manager was simple: a database of things to do, who should do them, and when they were due.
The problem was that my calendar did not know any of that.
The Missing Calendar Boundary
At the time, Notion did not give me a native way to expose those tasks to the calendar tools I already used every day. A due date inside Notion was useful only if I remembered to look at Notion. But the place where I expected dates, reminders, and daily commitments to show up was still my calendar.
I wanted the Notion database to remain the source of truth.
I also wanted the tasks to appear in Google Calendar, Outlook, and on my iPhone, alongside the rest of my actual day.
So when Notion opened the beta of its API, I jumped on it.
The first version of the project was not a library. It was a small companion service: an ASP.NET Web API that sat between my Notion task database and my calendar clients.
The service did one job:
- Query my task database through the Notion API.
- Convert the open tasks into an iCalendar document.
- Return that document as an
.icsfeed. - Let Google Calendar subscribe to the feed.
That was enough to close the gap.
Notion kept the structured workflow. The calendar kept being the place where my day became visible.
A Calendar Is Not a Task Manager
The important part of the project was not simply “show Notion dates in a calendar.”
The important part was task persistence.
Calendars are good at events. They are less good at unfinished work. If something was due yesterday and you missed it, the calendar usually lets it sink into the past. Technically the event is still there, but operationally it has disappeared from the surface where you make decisions today.
That is dangerous for tasks.
If I do not complete a task, I do not want it to vanish just because the date has passed. I want it to stay visible until I explicitly mark it as done.
So the service became opinionated:
If a task is open and its due date is in the past,
show it today.
That one rule changed the usefulness of the feed.
The calendar stopped being a passive export of due dates and became a lightweight reminder surface. Open work kept coming back into view. The feed did not try to replace a task manager, but it made the task manager harder to ignore.
Countdowns and Traffic Lights
There was another small behavior that mattered to me.
I did not only want to see a task on the day it was due. I wanted to see the deadline approaching.
In my Notion database, I had a column that controlled how many days in advance a task should start appearing. The companion service used that value to surface upcoming work before the due date. A task could enter the calendar gradually, with a countdown-like label that made the remaining time visible.
I also added a kind of traffic-light signal.
As the due date got closer, and then passed, the task title carried a visual priority cue. Green meant there was still room. Yellow meant attention was needed. Red meant the thing was overdue and should not be quietly ignored.
That logic lived partly in Notion formulas and partly in the feed generation. The current library still expects a Priority formula string and uses it as a prefix in the calendar event summary. That keeps the package small while preserving the useful trick: Notion can compute contextual text, and the calendar feed can expose it where I will actually see it.
Extracting the Core
I used that companion service for years.
I am still using it.
But after enough time, the shape of the code became clearer. There was the private application, with my configuration, my deployment, my controller, and my exact Notion database. Then there was the reusable core: query Notion, map open tasks, keep overdue tasks visible, and generate a valid iCalendar feed.
That core did not need to stay trapped inside my personal service.
So I extracted it into a small .NET library: Notion2Ical.
The library is intentionally narrow. It is not a full Notion SDK. It does not try to solve two-way sync. It does not try to support every possible database shape. It assumes a task database with a few known properties:
Namefor the task title.Due Datefor the calendar date.Priorityfor the prefix shown before the title.Statusto decide whether the task is still open.
Tasks whose status is not Done 🙌 are included in the feed. If an open task is overdue, the generated event is moved to today. Event UIDs are derived from Notion page IDs, so calendar clients can keep recognizing the same task across refreshes.
That is the useful part of the old system, packaged without the rest of my personal infrastructure.
Using It From ASP.NET
The package is designed to be hosted by an application you control.
You create a Notion internal integration, share the database with it, configure the library with the integration token and database ID, and expose the generated feed from an endpoint.
The registration is deliberately small:
services.AddNotionCalendarFeed(new NotionCalendarOptions
{
AccessToken = Configuration["Notion:AccessToken"],
DatabaseId = Configuration["Notion:DatabaseId"],
CalendarName = "Notion Tasks"
});
From there, an ASP.NET controller can inject INotionService and return the generated feed as text/calendar.
That application remains yours. The package only provides the reusable mechanics.
I like that boundary. Calendar feeds often contain personal information, and Notion integration tokens should be treated carefully. A self-hosted endpoint keeps the deployment model explicit: your Notion database, your token, your URL, your calendar subscription.
What the Library Actually Does
Internally, the flow is still deliberately boring.
The repository calls the Notion database query endpoint and asks for every item whose status property does not match the configured done value. It handles pagination, uses the configured Notion API version, and keeps the Notion-specific response models internal to the package.
The service then turns each returned page into a calendar event:
- it reads the start and optional end from the
Due Dateproperty; - it detects whether the Notion date contains a time or is an all-day date;
- it skips items without a usable due date or page ID;
- it moves overdue open tasks to today’s date;
- it gives timed events a default one-hour duration when needed;
- it gives all-day events a one-day
DTEND; - it builds the summary from the
Priorityformula string plus the task title; - it adds the Notion page URL to the event description;
- it derives the event UID from the Notion page ID.
That last point matters more than it looks. Calendar clients refresh subscribed feeds repeatedly. If the UID changes every time, the client cannot reliably understand that it is seeing the same task again. Using the Notion page ID as the stable identity keeps refreshes predictable.
The iCalendar side is small too, but it handles the details that are easy to forget when a personal script starts growing: CRLF line endings, all-day dates versus UTC timestamps, text escaping for commas, semicolons and newlines, and a stable VCALENDAR wrapper around the generated VEVENT entries.
None of this is complex. That is the point.
The package takes the fussy glue code that made my private companion service useful and gives it a clear, testable boundary.
Packaging an Old Personal Tool
Turning the code into a NuGet package was also a useful cleanup exercise.
The original service was exactly what I would expect from a personal tool that had quietly worked for years: a direct Notion API client, simple iCalendar string generation, hardcoded assumptions, little ceremony, and enough documentation for me to remember what I had done.
That is fine for personal infrastructure.
It is not the shape I want for code someone else might install.
The extracted repository now has:
- a solution with
Notion2IcalandNotion2Ical.Tests; - a small public API around
INotionServiceandNotionCalendarOptions; - internal Notion API DTOs and iCalendar output models;
- tests around service behavior, repository queries, iCalendar escaping, and dependency injection registration;
- CI that restores, builds, tests, and packs the library;
- a release workflow that publishes to NuGet from version tags;
- NuGet Trusted Publishing, so there is no long-lived NuGet API key stored in GitHub.
The package is available on NuGet:
https://www.nuget.org/packages/Notion2Ical/
The first release was small on purpose. The next release already had the kind of minor packaging fix that real packages tend to need: adding license metadata after NuGet warned about it.
That is not glamorous work, but it is part of turning private utility code into something other people can reasonably consume.
Why This Still Exists
Notion Calendar exists now, and it covers part of the problem that originally pushed me to build this.
If all you need is to see Notion database items inside Notion’s own calendar experience, the native product is much better than the Notion I started using in 2020. Notion Calendar can connect a database that has a date property, show those dated pages in the calendar, let you edit some database properties from the calendar, and apply status or date filters to the connected database view.
That is a real improvement.
But this library is about a different boundary.
It is about exposing a Notion task database as a generic iCalendar feed that any calendar client can subscribe to.
That matters if your calendar life is not fully inside Notion. It matters if you want tasks from Notion to show up next to meetings, family events, reminders, and everything else that already lives in Google Calendar, Outlook, Apple Calendar, Fantastical, or another client.
It also matters if you care about the task behavior more than the calendar view.
As of July 2026, Notion’s own documentation describes database items appearing in Notion Calendar, and also notes that connected Notion databases can only be viewed in Notion Calendar, not in Google Calendar or iCloud Calendar. Notion reminders can notify you at a chosen time, and inline reminders can turn red when they become today or overdue. What I do not see documented as native Notion Calendar behavior is the specific workflow this tool was built around: show a configurable countdown before the due date, then keep an unfinished task visible on today’s calendar date until its status changes to done.
The real feature is not the .ics file.
The real feature is that unfinished work remains visible.
Small Tools With a Long Life
I have a soft spot for tools like this.
They are not platforms. They are not ambitious products. They do not try to generalize too early.
They encode one workflow that was annoying enough to fix and useful enough to keep.
Notion2Ical started as a companion service for my own task manager. Years later, it is still solving the same problem. The difference is that the reusable part now has a cleaner boundary, tests, documentation, CI, and a package that can be installed without copying code out of my old application.
That feels like the right kind of maturity for a small personal tool.
It stayed small.
It became easier to reuse.
And it still does the thing I needed from the beginning: take the work I manage in Notion and make sure it shows up where I plan my day.
What I Might Do Next
The library is still intentionally minimal, but a few improvements would make sense:
- make more Notion property names configurable;
- expose better diagnostics when a database item cannot be converted;
- support richer iCalendar fields;
- add integration tests against recorded Notion API payloads;
- document a complete ASP.NET sample application.
I do not know yet whether it should grow much further.
For now, I am happy with the shape: one Notion database in, one iCalendar feed out, enough tests to keep it honest, and a NuGet package that lets someone else build their own companion service around the same core idea.
References
- NuGet package: https://www.nuget.org/packages/Notion2Ical/
- Source repository: https://github.com/lesbass/notion-2-ical
- Notion Calendar with Notion databases: https://www.notion.com/help/use-notion-calendar-with-notion
- Notion Calendar getting started: https://www.notion.com/help/guides/getting-started-with-notion-calendar
- Notion reminders: https://www.notion.com/help/reminders
- Notion API documentation: https://developers.notion.com/
- Notion database query API: https://developers.notion.com/reference/post-database-query
- NuGet Trusted Publishing: https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing
Share