# Create Apple Reminders with EventKit on Mac

Canonical: https://figment.so/blog/apple-reminders-eventkit

EventKit is Apple's framework for reading and writing calendar events and reminders. On macOS you can call it from a Swift app, command-line tool, or background helper process to create, update, and complete reminders that iCloud then syncs to every signed-in device — iPhone, iPad, Mac, Apple Watch, and Apple Vision Pro.

## What EventKit gives you on macOS

EventKit exposes `EKEventStore` as the single entry point to both Calendar and Reminders data. For reminders, the key types are:

- **`EKReminder`** — a single reminder with a title, optional due-date components, notes, priority, alarms, and recurrence rules
- **`EKCalendar`** — what EventKit calls a list (what Reminders.app labels a "List")
- **`EKAlarm`** — a time-based or location-based notification attached to a reminder
- **`EKRecurrenceRule`** — a repeat schedule: daily, weekly, monthly, yearly, or a custom cadence

Changes written through EventKit to an iCloud-backed list sync across all connected devices once iCloud propagates them. Source: [iCloud Reminders guide](https://support.apple.com/en-gb/guide/icloud/mm203ae070a2/icloud).

## macOS project setup: Info.plist and sandbox entitlement

Two Xcode settings are required before any runtime call will succeed.

**Info.plist usage description**

Add `NSRemindersFullAccessUsageDescription` with a plain-English explanation of why your app needs access. This key is required from macOS 14 (Sonoma) and iOS 17 onwards. Apps targeting macOS 13 and earlier use the deprecated `NSRemindersUsageDescription` key instead.

```xml
<key>NSRemindersFullAccessUsageDescription</key>
<string>This app creates and reads your Reminders to manage tasks.</string>
```

**App Sandbox entitlement (sandboxed Mac apps)**

If your app uses App Sandbox — required for Mac App Store distribution — enable the **Calendars** entry in Xcode's Signing & Capabilities tab. The underlying entitlement key is `com.apple.security.personal-information.calendars`. This single entitlement covers both Calendar and Reminders access on macOS. Command-line tools and daemons that do not opt into App Sandbox do not need this entitlement but still require the system-level permission granted through `requestFullAccessToReminders()`.

## Request Reminders authorization

macOS 14 (Sonoma) and iOS 17 introduced `requestFullAccessToReminders()` and deprecated the older `requestAccess(to: .reminder, completion:)` callback. The new method throws and returns a `Bool`, fitting naturally into async/await code. Source: [requestFullAccessToReminders(completion:)](https://developer.apple.com/documentation/eventkit/ekeventstore/requestfullaccesstoreminders(completion:)).

```swift
import EventKit

let store = EKEventStore()

func authorizeReminders() async throws -> Bool {
    let status = EKEventStore.authorizationStatus(for: .reminder)
    switch status {
    case .fullAccess:
        return true          // Granted on macOS 14+ / iOS 17+
    case .authorized:        // Deprecated alias; still returned by pre-macOS-14 SDK builds
        return true
    case .notDetermined:
        return try await store.requestFullAccessToReminders()
    case .denied, .restricted:
        return false
    @unknown default:
        return false
    }
}
```

Checking `authorizationStatus(for:)` before calling the request method avoids showing the system permission dialog when access is already granted. The `.fullAccess` status case was introduced in iOS 17 and macOS 14. The older `.authorized` case is kept as an explicit branch rather than a fallthrough so that pre-macOS-14 SDK builds behave correctly.

## Create a reminder with a due date and alarm

This is the core artifact. `EKReminder` stores due times as `DateComponents`, not a plain `Date`, which preserves the user's intended time without timezone rounding.

```swift
/// Creates a reminder on the specified list (or the default list if nil).
/// Call only after authorizeReminders() returns true.
func createReminder(
    title: String,
    dueDate: Date,
    notes: String? = nil,
    list: EKCalendar? = nil
) throws {
    let reminder = EKReminder(eventStore: store)
    reminder.title = title
    reminder.notes = notes

    // EKReminder uses DateComponents for due times, not a plain Date.
    reminder.dueDateComponents = Calendar.current.dateComponents(
        [.year, .month, .day, .hour, .minute],
        from: dueDate
    )

    // relativeOffset is in seconds; -600 fires 10 minutes before the due time.
    reminder.addAlarm(EKAlarm(relativeOffset: -600))

    // Assign a list; fall back to the user's configured default.
    reminder.calendar = list ?? store.defaultCalendarForNewReminders()

    // commit: true writes immediately.
    // Pass commit: false when batching multiple reminders into one write.
    try store.save(reminder, commit: true)
}
```

A few details worth calling out:

- `store.defaultCalendarForNewReminders()` can return `nil` when no iCloud or local list is configured. Guard or unwrap before passing it to `reminder.calendar`.
- A positive `relativeOffset` fires the alarm after the due time. Use `EKAlarm(absoluteDate: someDate)` to fire at a fixed point in time, independent of the due date.
- `commit: false` is useful when creating several reminders in a loop. Call `store.commit()` once at the end rather than writing to disk on every iteration.

## Organize reminders into lists

EventKit exposes Reminders "Lists" as `EKCalendar` instances scoped to `.reminder`. You can read all visible lists, write to the default, or create a new synced list.

```swift
// All Reminders lists visible to this store
let allLists = store.calendars(for: .reminder)

// Create a new list backed by iCloud so it syncs to all devices.
// Throws if no writable source is found.
func createList(named title: String) throws -> EKCalendar {
    let list = EKCalendar(for: .reminder, eventStore: store)
    list.title = title

    // EKSourceType.calDAV covers both third-party CalDAV and iCloud sources.
    // Fall back to the default calendar's source when no iCloud source is found.
    guard let source = store.sources.first(where: {
        $0.sourceType == .calDAV && $0.title.lowercased().contains("icloud")
    }) ?? store.defaultCalendarForNewReminders()?.source else {
        throw CocoaError(.featureUnsupported)   // No writable source available
    }
    list.source = source

    try store.saveCalendar(list, commit: true)
    return list
}
```

Reminders stored on a CalDAV / iCloud source sync across the user's devices. Reminders on a local source stay on the Mac.

## Repeat with recurrence rules

Attach an `EKRecurrenceRule` to repeat a reminder on a schedule. This example fires every Monday for 12 occurrences:

```swift
let rule = EKRecurrenceRule(
    recurrenceWith: .weekly,
    interval: 1,
    daysOfTheWeek: [EKRecurrenceDayOfWeek(.monday)],
    daysOfTheMonth: nil,
    monthsOfTheYear: nil,
    weeksOfTheYear: nil,
    daysOfTheYear: nil,
    setPositions: nil,
    end: EKRecurrenceEnd(occurrenceCount: 12)
)
reminder.addRecurrenceRule(rule)
```

The Reminders app displays one recurrence rule per reminder. To change a rule, call `removeRecurrenceRule(_:)` first, then add the replacement.

## Read Reminders via Figment Apple Connect

EventKit is the right tool when you ship a native macOS binary you control. For a web app, automation pipeline, or agent that cannot take on a native build, Figment Apple Connect offers a user-approved, scoped connection to Apple data running on a signed-in Mac.

Figment's current Reminders support is a **synthetic read-only sandbox**. It returns sample reminder data for integration development. Live read access to a user's real reminders is not yet available to customers, and no write, create, or delete endpoint is supplied. The sandbox requires the Reminders service on the sandbox connection and an active `reminders:read` grant.

Sandbox request:

```bash
curl -s "https://figment.so/api/apple-connect/v1/connections/con_xyz/reminders?completed=false&limit=20" \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

Sample response (synthetic data — does not read real Reminders):

```json
{
  "data": [
    {
      "id": "rem-001",
      "calendarId": "cal-abc",
      "title": "Review project proposal",
      "completed": false,
      "priority": 1,
      "due": "2026-11-14T09:00:00Z"
    }
  ],
  "mode": "sandbox",
  "hasMore": false,
  "nextOffset": null,
  "scope": "synthetic sandbox data"
}
```

The `completed` query parameter accepts `true` or `false` and defaults to `false`. Optional `limit` (1–100, default 20) and `offset` (0–10000) control pagination; follow `hasMore` and `nextOffset` rather than guessing a count.

Register your app and start with the free sandbox at [figment.so/apple-connect/developers](/apple-connect/developers). For a broader view of Apple data access via Figment — including the live iMessage REST API — see the [OAuth for iCloud overview](/oauth-for-icloud).
