Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Create Apple Reminders with EventKit on Mac

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.

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.

<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:).

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.

/// 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.

// 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:

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:

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):

{
  "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. For a broader view of Apple data access via Figment — including the live iMessage REST API — see the OAuth for iCloud overview.


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms