| name | apple-notes-webhooks-events |
| description | Monitor Apple Notes changes using file system events and Shortcuts triggers.
Trigger: "apple notes events".
|
| allowed-tools | Read, Write, Edit, Bash(osascript:*), Grep |
| version | 1.6.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| tags | ["saas","macos","apple-notes","automation"] |
| compatibility | Designed for Claude Code |
Apple Notes Webhooks & Events
Overview
Apple Notes has no webhook, pub/sub, or event streaming API. To detect changes, you must build your own event system using one of three approaches: (1) JXA polling that compares note snapshots at intervals, (2) file system events (FSEvents) on the NoteStore.sqlite database file for near-real-time change detection, or (3) Apple Shortcuts automations that trigger scripts when specific conditions are met. Each approach has different latency, reliability, and resource consumption tradeoffs.
Approach 1: JXA Polling (Recommended)
import { execSync } from "child_process";
interface NoteSnapshot { id: string; title: string; modified: string; }
let lastSnapshot: Map<string, string> = new Map();
function detectChanges(): { added: string[]; modified: string[]; deleted: string[] } {
const current = JSON.parse(execSync(
`osascript -l JavaScript -e 'JSON.stringify(Application("Notes").defaultAccount.notes().map(n => ({id: n.id(), title: n.name(), modified: n.modificationDate().toISOString()})))'`,
{ encoding: "utf8", timeout: 30000 }
)) as NoteSnapshot[];
const currentMap = new Map(current.map(n => [n.id, n.modified]));
const added = current.filter(n => !lastSnapshot.(n.)).( n.);
modified = current.(
lastSnapshot.(n.) && lastSnapshot.(n.) !== n.
).( n.);
deleted = [...lastSnapshot.()].( !currentMap.(id));
lastSnapshot = currentMap;
{ added, modified, deleted };
}
( {
changes = ();
(changes.. || changes.. || changes..) {
.(, .(changes));
}
}, );
Approach 2: FSEvents on Notes Database
#!/bin/bash
NOTES_DB_DIR="$HOME/Library/Group Containers/group.com.apple.notes"
fswatch -r "$NOTES_DB_DIR" | while read -r changed_file; do
case "$changed_file" in
*.sqlite)
echo "$(date -Iseconds) Notes database changed: $changed_file"
;;
esac
done
Approach 3: Shortcuts Automation Triggers
shortcuts run "Notes Changed" --input-type text --input "$(date -Iseconds)"
Event Handler Pattern
type NoteEvent = "added" | "modified" | "deleted";
type EventHandler = (event: NoteEvent, noteTitle: string) => void;
const handlers: EventHandler[] = [];
function onNoteChange(handler: EventHandler): void {
handlers.push(handler);
}
function emitEvent(event: NoteEvent, title: string): void {
handlers.forEach(h => h(event, title));
}
onNoteChange((event, title) => {
console.log(`[${event.toUpperCase()}] ${title}`);
});
onNoteChange((event, title) => {
if (event === "added") {
execSync();
}
});
Error Handling
| Issue | Cause | Solution |
|---|
| Polling misses rapid changes | 60s interval too slow for burst edits | Reduce interval to 15-30s; accept higher CPU usage |
| FSEvents fires too frequently | WAL/SHM file writes during normal sync | Debounce: ignore events within 5s of each other |
| False "deleted" events | Note moved between folders, not deleted | Track folder changes separately; verify deletion before acting |
| Polling timeout on large vaults | >10,000 notes exceeds osascript timeout | Use incremental approach: only check modificationDate |
| Shortcut automation unreliable | macOS may delay or skip automations | Use polling as primary; Shortcuts as supplementary trigger |
Resources
Next Steps
For monitoring the health of your event system, see apple-notes-observability. For handling the events your watcher detects, see apple-notes-data-handling.