---
title: "Use calendars in your app"
description: "Store events with recurrence and time zones, import and export iCalendar, and show free/busy without showing details."
section: "guides"
status: "Building for 1.2"
spec: "calendar"
canonical: "https://mainsdk.dev/docs/guides/use-calendar"
updated: "2026-10-28"
---

# Use calendars in your app

> Store events with recurrence and time zones, import and export iCalendar, and show free/busy without showing details.

Status: Building for 1.2 · Canonical: https://mainsdk.dev/docs/guides/use-calendar · Updated: 2026-10-28

You will add calendars to your app: a team calendar with a weekly series that keeps its local time through daylight saving, a one-off edit, an iCalendar import and a free/busy answer. It takes about 20 minutes.

> **Note:** `calendar` is experimental (spec `calendar`) and runs from the repository; it is not published to npm.

## You need

- A MainSDK app with a `main.yaml` and a `db` section (see [Thirty-minute path](https://mainsdk.dev/docs/getting-started/thirty-minutes)).
- Users that have an id. The calendar checks `access` for each caller.

## 1. Declare the calendar

```yaml title="main.yaml"
calendar:
  store: db
  max-window: 366d
```

Build a `Calendar` once from the experimental subpath, with your stores, tenant and subject:

```ts
import { Calendar } from "mainsdk/experimental/calendar";

type CalendarDeps = ConstructorParameters<typeof Calendar>[0];

declare const config: Record<string, unknown>;      // the calendar section of main.yaml
declare const stores: CalendarDeps["stores"];         // the region SQL stores your db uses
declare const tenant: CalendarDeps["tenant"];
declare const currentUser: { id: string };

const calendar = new Calendar({ config, stores, tenant, subject: () => ({ id: currentUser.id }) });
await calendar.createCalendar({ id: "team", name: "Team" });
```

## 2. Add a weekly series

```ts
import type { Calendar } from "mainsdk/experimental/calendar";

declare const calendar: Calendar;

await calendar.create("team", {
  title: "Stand-up", start: "2026-10-26T09:00", end: "2026-10-26T09:15",
  timeZone: "America/New_York", rrule: "FREQ=WEEKLY;BYDAY=MO",
});
```

The time is stored as local time plus the zone, so 26 October is 13:00Z and 2 November is 14:00Z.

## 3. List a window

```ts
import type { Calendar } from "mainsdk/experimental/calendar";

declare const calendar: Calendar;

const { items } = await calendar.list({ calendars: ["team"], from: "2026-10-25", to: "2026-11-30" });
```

Each occurrence carries UTC instants and the local time.

## 4. Edit one occurrence

```ts
import type { Calendar } from "mainsdk/experimental/calendar";

declare const calendar: Calendar;
declare const eventId: string;
declare const items: { occurrence: string }[];

await calendar.update(eventId, { start: "2026-11-09T11:00", end: "2026-11-09T11:15" }, {
  scope: "this", occurrence: items[2].occurrence, version: 1,
});
```

Use `following` to split the series and `all` to change every occurrence. Pass the `version` you read; a stale one fails with `MAIN-CAL-0008`.

## 5. Import, export and free/busy

```ts
import type { Calendar } from "mainsdk/experimental/calendar";

declare const calendar: Calendar;
declare const icsText: string;

await calendar.import("team", icsText);          // events with the same UID update
const text = await calendar.export("team");
const { busy } = await calendar.freeBusy({ calendars: ["team"], from: "2026-11-02T00:00", to: "2026-11-03T00:00" });
```

Free/busy returns merged intervals and never a title, place or attendee.

## Check it worked

- `pnpm mainsdk check` passes, with no `MAIN-CAL-0001` or `MAIN-CAL-0002` finding in your seed data.
- The weekly series lists at 09:00 local on both sides of the November change.
- A person who may only see free/busy gets intervals with no detail.

## If it fails

- `MAIN-CAL-0001`: an unknown zone or an end before the start. Use an IANA zone such as `America/New_York`.
- `MAIN-CAL-0002`: a recurrence part that is not supported, such as `BYWEEKNO`. The error names the part.
- `MAIN-CAL-0003`: the window is wider than `max-window`. Ask for a narrower one.

## Prompt for your coding agent

> Add calendars to my app with MainSDK's `calendar` interface. Store events in an IANA zone with weekly recurrence, let people edit one occurrence or the following ones with a version check, import and export iCalendar, and answer free/busy without titles.
