A lightweight Raycast plugin whose sole purpose is to surface today's Apple Reminders status at a glance — directly from the Raycast launcher.
- Today's To-Do count — shows how many reminders are due today and not yet completed.
- Done count — shows how many of today's reminders have already been marked complete.
- Congratulations state — when every reminder for today is done, the plugin replaces the counters with a celebratory message confirming all tasks were completed.
Raycast is the power-user launcher on macOS. Apple Reminders holds the day's tasks, but there is no native way to see a quick summary count without opening the Reminders app. This plugin brings that summary into the command palette so the user can check their daily progress in under a second without leaving their keyboard-driven workflow.
- Raycast Extensions API (TypeScript / React) — the standard framework for building Raycast commands.
- Swift + EventKit bridge via
@raycast/extensions-swift-tools— reads today's reminders natively from the system using Apple's EventKit framework, compiled into a local binary that the extension calls at runtime. This is the same approach used by the officialapple-remindersextension. - Node.js (bundled by Raycast) — runs the extension process.
- A single Raycast Menu Bar Command that shows:
🔲 Remaining: Y ✅ Done: Xin the menu bar title- Or
🎉 All done! Great work today.when Y = 0 and X > 0 and there were tasks.
- Auto-refreshes via Raycast background refresh (
intervalin package.json). - No write access — read-only view of Reminders data.
- Creating, editing, or deleting reminders.
- Multi-day or list-specific views.
- Notifications or badges.
- Getting started: https://developers.raycast.com/basics/getting-started
- Menu Bar Commands: https://developers.raycast.com/api-reference/menu-bar-commands
- Manifest / package.json: https://developers.raycast.com/information/manifest
package.json — command entry:
{
"name": "today-reminders",
"title": "Today's Reminders",
"description": "Shows today's reminder count in the menu bar",
"mode": "menu-bar",
"interval": "5m"
}
intervalconfigures background refresh. Minimum value is1m.
Basic MenuBarExtra shape (TypeScript/React):
import { MenuBarExtra } from "@raycast/api";
export default function Command() {
return (
<MenuBarExtra title="🔲 3 ✅ 2" tooltip="Today's Reminders">
<MenuBarExtra.Item title="You're making great progress!" />
</MenuBarExtra>
);
}Key rules for Menu Bar Commands:
isLoading={true}keeps the command alive during async data fetching; set it tofalsewhen done.- Commands are not long-lived processes — Raycast loads and unloads them on demand.
- Use
useCachedPromiseoruseCachedStatefrom@raycast/utilsfor fast cached rendering.
The official apple-reminders extension uses a Swift binary to access Apple's EventKit framework. This is the recommended approach for native Reminders access.
Import syntax in TypeScript:
import { getReminders, ... } from "swift:../swift/AppleReminders";Defining an exported Swift function:
import RaycastSwiftMacros
import EventKit
@raycast func getTodayReminders() async throws -> [ReminderItem] {
// EventKit query filtered to today
}Rules for @raycast-decorated functions:
- Parameters must conform to
Decodable; return types toEncodable - Only global functions are exported (not struct/class methods)
- Functions can be
asyncand/orthrows
Package.swift dependencies:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "AppleReminders",
platforms: [.macOS(.v12)],
dependencies: [
.package(url: "https://github.com/raycast/extensions-swift-tools", from: "1.0.4")
],
targets: [
.executableTarget(
name: "AppleReminders",
dependencies: [
.product(name: "RaycastSwiftMacros", package: "extensions-swift-tools"),
.product(name: "RaycastSwiftPlugin", package: "extensions-swift-tools"),
.product(name: "RaycastTypeScriptPlugin", package: "extensions-swift-tools"),
]
),
]
)Xcode must be installed on the user's machine to compile the Swift target.
Repo (pinned commit): https://github.com/raycast/extensions/tree/769fcfe527c243875b4e86b8cdce33352f94080b/extensions/apple-reminders/
Key files to reference:
| File | Purpose |
|---|---|
src/menu-bar.tsx |
Full menu bar command — filtering by today/overdue, section rendering, count logic |
src/hooks/useData.tsx |
Data-fetching hook that calls the Swift bridge and returns { data, isLoading, mutate } |
src/helpers.ts |
isToday(), isOverdue(), isTomorrow() date helpers |
swift/AppleReminders/ |
Swift package — EventKit queries, @raycast exported functions |
package.json |
Extension manifest with menu-bar mode, preferences, and background interval |
Reminder data shape (from blueprint):
type Reminder = {
id: string;
title: string;
isCompleted: boolean;
dueDate?: string; // ISO date string, optional
list?: { id: string; title: string; color: string };
priority?: "high" | "medium" | "low" | null;
openUrl: string;
};Filtering today's reminders (from blueprint menu-bar.tsx):
import { isToday, isOverdue } from "./helpers";
const todayReminders = reminders.filter(
(r) => r.dueDate && (isToday(r.dueDate) || isOverdue(r.dueDate))
);
const done = todayReminders.filter((r) => r.isCompleted).length;
const remaining = todayReminders.filter((r) => !r.isCompleted).length;Congratulations state logic:
const allDone = remaining === 0 && done > 0;
const title = allDone
? "🎉 All done!"
: `🔲 ${remaining} ✅ ${done}`;Full source code from the official apple-reminders extension has been saved locally in reference/ for offline implementation. These are the working, production-grade files to base our code on:
| Local file | Blueprint source | What to take from it |
|---|---|---|
reference/blueprint-menu-bar.tsx |
src/menu-bar.tsx |
MenuBarExtra structure, useMemo filtering, isLoading handling, sections pattern |
reference/blueprint-helpers.ts |
src/helpers.ts |
isToday(), isOverdue() — copy directly, they handle both full-day and datetime formats |
reference/blueprint-Reminders.swift |
swift/.../Reminders.swift |
getData() function, Reminder/RemindersData structs, EventKit access pattern |
reference/blueprint-EventKit+Additions.swift |
swift/.../EventKit+Additions.swift |
EKReminder.toStruct(), date formatters, fetchReminders async wrapper |
reference/blueprint-package.json |
package.json |
Dependency versions, scripts, menu-bar command config with interval |
Important for implementation: The blueprint's getData() fetches only incomplete reminders. To get today's done count, we also need getCompletedReminders() (also in the blueprint). Both are already saved in reference/blueprint-Reminders.swift.
- Raycast installed on macOS
- Node.js 22.14+ (use
nvmto manage versions) - Xcode installed (needed to compile the Swift/EventKit bridge)
cd reminders-plugin
npm install # install dependencies
npm run dev # start development servernpm run dev (which runs ray develop under the hood):
- Builds the TypeScript and compiles the Swift package.
- Registers the extension with Raycast under a "Development" section in root search.
- Hot-reloads on every file save — changes appear instantly in Raycast.
- Logs
console.log()output to the terminal. - Shows an error overlay inside Raycast for unhandled exceptions.
The Swift binary is compiled on first run and cached. It only recompiles when Swift source files change.
| Scenario | How to verify |
|---|---|
| Happy path | Open Raycast → see menu bar icon with counts → compare against Reminders.app |
| Complete a reminder | Mark done in Reminders.app → wait for refresh (or re-trigger) → count updates |
| All done | Complete all today's reminders → menu bar shows 🎉 All done! |
| No reminders | Clear all today's reminders → menu bar shows empty/no-reminders state |
| Overdue | Create a reminder for yesterday → it should appear in "remaining" count |
| Permission denied | Revoke Reminders access in System Settings → extension shows access-denied message |
| Background refresh | Leave running → after interval (5m) counts auto-update without manual trigger |
- Console:
console.log()in TypeScript → visible in terminal runningnpm run dev - React DevTools: Press
⌘ ⌥ Dwhile command is focused in Raycast - Node.js debugger: Attach via VS Code extension for breakpoints
- Error overlay: Unhandled exceptions render as visual errors in Raycast (dev mode only)
npm run build # production build (ray build -e dist)
npm run lint # lint check
npm run fix-lint # auto-fix lint issues
npm run publish # publish to Raycast Store