| Application isn't running |
-600 |
App quit between calls |
Wrap in retry with Application("Notes").activate() first
Build automated note management workflows with Apple Notes JXA scripts.
ReadWriteEditBash(osascript:*)Bash(node:*)Grep
Apple Notes Core Workflow A — Note Management Automation
Overview
Primary workflow: automate Apple Notes management with batch creation, template-based note generation, folder organization, and content sync from external sources (Markdown files, RSS, calendar events).
Prerequisites
- Approved source files, a named target account and pre-created test folder, and a backup/reconciliation plan for bulk changes.
- Sanitization for external Markdown, feed, or calendar content before it becomes note HTML.
- A durable source-record key for every import or move so retries can be reconciled.
Instructions
- Start with synthetic data in the configured local test folder; do not use a default account or create production folders implicitly.
- Validate and sanitize each source record before conversion, then enqueue one mutation at a time with an idempotency key.
- Record only opaque source keys and outcomes, inspect a bounded sample, and reconcile counts before widening scope.
- Stop on a timeout or unexpected folder match; do not rerun a whole batch blindly.
Procedure
Step 1: Batch Note Creator from Markdown Files
#!/bin/bash
# scripts/markdown-to-notes.sh — Import Markdown files as Apple Notes
FOLDER_NAME="${1:-Imported}"
for md_file in *.md; do
[ -f "$md_file" ] || continue
title=$(head -1 "$md_file" | sed 's/^#\s*//')
# Convert Markdown to basic HTML
body=$(cat "$md_file" | sed 's/^# /<h1>/;s/$/<\/h1>/' | sed 's/^## /<h2>/;s/$/<\/h2>/' | sed 's/^- /<li>/;s/$/<\/li>/' | sed 's/^$/<br>/')
osascript -l JavaScript -e "
const Notes = Application('Notes');
const account = Notes.defaultAccount;
let folder = account.folders().find(f => f.name() === '$FOLDER_NAME');
if (!folder) {
folder = Notes.Folder({ name: '$FOLDER_NAME' });
account.folders.push(folder);
}
const note = Notes.Note({ name: '$title', body: \`$body\` });
folder.notes.push(note);
'Created: $title';
"
echo "Imported: $md_file → $title"
done
Step 2: Note Template Engine (JXA)
// scripts/note-template.js — Run with: osascript -l JavaScript scripts/note-template.js
const Notes = Application('Notes');
const TEMPLATES = {
meeting: (data) => `
<h1>${data.title || 'Meeting Notes'}</h1>
<p><strong>Date:</strong> ${new Date().toLocaleDateString()}</p>
<p><strong>Attendees:</strong> ${data.attendees || 'TBD'}</p>
<h2>Agenda</h2><ul><li></li></ul>
<h2>Action Items</h2><ul><li></li>&l
Export and convert Apple Notes to Markdown, JSON, HTML, and SQLite.
ReadWriteEditBash(osascript:*)Bash(node:*)Grep
Apple Notes Core Workflow B — Export & Conversion
Overview
Export Apple Notes to portable formats: Markdown, JSON, HTML files, and SQLite databases. Apple Notes stores content as HTML internally — these workflows convert it to developer-friendly formats.
Prerequisites
- Authorization for exact folders and fields, an encrypted owner-only destination, and a retention/deletion policy.
- A test export of synthetic notes and a checksum/reconciliation method.
- A decision on attachment handling; do not infer that content or attachment export is complete from JXA metadata.
Instructions
- Export one named scope at a time, minimize fields, and keep note content out of command history and console output.
- Create the destination with restrictive permissions before export; sanitize filenames and HTML before writing derived files.
- Verify record count and checksum, then encrypt or move the artifact according to the approved handling policy.
- Keep SQLite or search indexes private and time-bounded; they replicate sensitive note data.
Procedure
Step 1: Export All Notes to JSON
osascript -l JavaScript -e '
const Notes = Application("Notes");
const allNotes = Notes.defaultAccount.notes();
const exported = allNotes.map(n => ({
id: n.id(),
title: n.name(),
body: n.body(),
folder: n.container().name(),
created: n.creationDate().toISOString(),
modified: n.modificationDate().toISOString(),
}));
JSON.stringify(exported, null, 2);
' > apple-notes-export.json
echo "Exported $(jq length apple-notes-export.json) notes to apple-notes-export.json"
Step 2: Export Notes as Markdown Files
#!/bin/bash
# scripts/notes-to-markdown.sh
OUTPUT_DIR="${1:-./notes-export}"
mkdir -p "$OUTPUT_DIR"
osascript -l JavaScript -e '
const Notes = Application("Notes");
const notes = Notes.defaultAccount.notes();
notes.map(n => JSON.stringify({
title: n.name(),
body: n.body(),
folder: n.container().name(),
})).join("\n---SEPARATOR---\n");
' | while IFS= read -r line; do
if [ "$line" = "---SEPARATOR---" ]; then continue; fi
title=$(echo "$line" | jq -r '.title' 2>/dev/null)
body=$(echo "$line" | jq -r '.body' 2>/dev/null)
folder=$(echo "$line" | jq -r '.folder' 2>/dev/null)
# Convert HTML to basic Markdown
md_body=$(echo "$body" | sed 's/<h1>/# /g; s/<\/h1>//g; s/<h2>/## /g; s/<\/h2>//g; s/<p>//g; s/<\/p>/\n/g; s/<br>/\n/g; s/<li>/- /g; s/<\/li>//g; s/<[^>]*>//g')
safe_title=$(echo "$title" | tr '/:' '-' | head -c 100)
mkdir -p "$OUTPUT
Apple Notes cost optimization — it is free, focus on iCloud storage management.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Cost Tuning
Overview
Apple Notes itself is free with every Apple ID. The only real cost is iCloud storage, which is shared across Photos, iCloud Drive, Mail, Notes, and device backups. For automation workflows, the main cost drivers are large embedded attachments (images, PDFs, scans) that inflate iCloud usage, and the "On My Mac" account that uses local disk instead. Understanding what consumes storage lets you keep notes within the free 5 GB tier or choose the right iCloud+ plan for your organization.
Prerequisites
- Current pricing and storage policy from Apple or the organization; the table is illustrative and must not be used as a purchase quote.
- Approval for any archive, deletion, or account move, plus an encrypted backup and restore test.
- A scoped audit that reports aggregate measures only, not account names, note titles, or content.
Instructions
- Use System Settings or the iCloud account owner as the source of truth for current capacity and plan decisions.
- Measure aggregate storage indicators for an approved scope; avoid exporting or enumerating unrelated notes to estimate cost.
- Propose a reversible archive plan with retention, encryption, reconciliation, and explicit deletion approval.
- Do not move notes between accounts automatically—account moves can change sharing, sync, and recovery behavior.
iCloud Storage Tiers
| Plan |
Storage |
Price/mo |
Approx Notes Capacity |
| Free |
5 GB |
$0 |
~50,000 text-only notes |
| iCloud+ 50 GB |
50 GB |
$0.99 |
Unlimited text; moderate attachments |
| iCloud+ 200 GB |
200 GB |
$2.99 |
Shared with Family Sharing |
| iCloud+ 2 TB |
2 TB |
$9.99 |
Enterprise/heavy media |
| iCloud+ 6 TB |
6 TB |
$29.99 |
Large teams with shared albums + notes |
| iCloud+ 12 TB |
12 TB |
$59.99 |
Maximum tier |
Storage Audit Script
#!/bin/bash
# Audit Apple Notes storage consumption
echo "=== iCloud Storage Overview ==="
# Total iCloud usage (approximate from system)
df -h ~/Library/Mobile\ Documents/ 2>/dev/null | tail -1
echo ""
echo "=== Notes Content Audit ==="
osascript -l JavaScript -e '
const Notes = Application("Notes");
const accounts = Notes.accounts();
let report = [];
accounts.forEach(a => {
const notes = a.notes();
let totalChars = 0;
let withAttachments = 0;
notes.forEach(n => {
totalChars += n.body().length;
if (n.attachments().length > 0) withAttachments++;
});
report.push(a.name() + ": " + notes.length + &
Handle Apple Notes data formats: HTML body, attachments, and rich content.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Data Handling
Overview
Apple Notes stores note content as a restricted subset of HTML internally. The body() property in JXA returns this HTML, which includes <div>, <h1>-<h3>, <b>, <i>, <ul>, <li>, and Apple-specific classes for checklists and tables. Attachments (images, PDFs, sketches, scans) are embedded as <img> or object references but cannot be directly extracted via JXA — they require the attachments() property. Understanding these data formats is essential for building reliable import, export, and backup pipelines.
Prerequisites
- Written authorization for the accounts, folders, and note categories to be exported or transformed.
- An encrypted destination outside a shared directory, a retention limit, and a tested restore path.
- A conversion test corpus containing only synthetic notes; do not use the examples as proof that Apple Notes HTML is a stable public format.
Instructions
- Scope reads to named folders and minimize collected fields before invoking
osascript.
- Write exports to a pre-created, owner-only directory and validate permissions before any data is emitted.
- Treat HTML and attachment metadata as untrusted content: sanitize before rendering, and never execute embedded links or markup.
- Hash or redact identifiers in operational logs; record only counts and completion state.
Note Body HTML Format
<!-- Apple Notes uses a subset of HTML wrapped in <div> blocks -->
<div><h1>Title</h1></div>
<div><br></div>
<div>Paragraph text here.</div>
<div><b>Bold text</b> and <i>italic text</i></div>
<div><br></div>
<div><ul><li>List item 1</li><li>List item 2</li></ul></div>
<!-- Checklists use Apple's custom class -->
<div><ul class="com-apple-note-checklist">
<li class="done">Completed item</li>
<li>Incomplete item</li>
</ul></div>
<!-- Tables (macOS Ventura+) use standard HTML tables -->
<div><table><tr><td>Cell 1</td><td>Cell 2</td></tr></table></div>
<!-- Tags (macOS Sonoma+) are stored as hashtags in body text -->
<div>#project #important</div>
Export All Notes to JSON
#!/bin/bash
# Full export with metadata — useful for backups and migration
osascript -l JavaScript -e '
const Notes = Application("Notes");
const results = Notes.defaultAccount.notes().map(n => ({
id: n.id(),
Collect Apple Notes automation debug evidence for troubleshooting.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Debug Bundle
Overview
This debug bundle collects diagnostic information from Apple Notes automation integrations for troubleshooting AppleScript and JXA (JavaScript for Automation) workflows. It captures macOS version compatibility, Notes.app account configuration, folder and note counts, TCC (Transparency, Consent, and Control) permission status, and Shortcuts automation entitlements. The resulting tarball helps diagnose permission denials, sandbox restrictions, iCloud sync failures, and scripting bridge errors that commonly block Notes automation.
Prerequisites
- macOS 12+ with Notes.app configured
osascript, tar available (built into macOS)
- Terminal granted Automation permission for Notes.app in System Preferences > Privacy & Security
Instructions
- Obtain incident-owner approval and choose a private, access-controlled destination before collection.
- Collect platform version, job configuration, and redacted error classifications first; include account or folder metadata only when essential to diagnosis.
- Exclude note titles, bodies, attachment names, TCC database rows, Keychain values, and full home-directory listings.
- Encrypt the bundle in transit, share it only with the incident responders, and delete it at the documented retention deadline.
Debug Collection Script
#!/bin/bash
set -euo pipefail
BUNDLE="debug-apple-notes-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUNDLE"
# Environment check
echo "=== Environment ===" > "$BUNDLE/environment.txt"
echo "macOS: $(sw_vers -productVersion 2>/dev/null || echo 'not macOS')" >> "$BUNDLE/environment.txt"
echo "Notes.app running: $(pgrep -x Notes > /dev/null && echo Yes || echo No)" >> "$BUNDLE/environment.txt"
echo "Shell: $SHELL ($TERM)" >> "$BUNDLE/environment.txt"
echo "Timestamp: $(date -u)" >> "$BUNDLE/environment.txt"
# Automation permissions: record only a read-only authorization outcome.
echo "=== Automation Authorization ===" > "$BUNDLE/tcc-status.txt"
osascript -l JavaScript -e 'Application("Notes").name(); "authorized"' \
>> "$BUNDLE/tcc-status.txt" 2>&1 || echo "authorization check failed" >> "$BUNDLE/tcc-status.txt"
# Scoped authorization outcome via JXA; do not enumerate accounts or folders.
echo "=== Scoped Notes Access ===" > "$BUNDLE/accounts.txt"
osascript -l JavaScript -e '
const app = Application("Notes");
app.name();
"scoped access available";
' >> "$BUNDLE/accounts.txt" 2>&1 || echo "JXA scoped query failed" >> "$BUNDLE
Deploy Apple Notes automation as a local macOS service.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Deploy Integration
Overview
Apple Notes automation runs exclusively on macOS — there is no cloud deployment path because Notes.app depends on the local Apple Events subsystem and TCC permissions. Deployment means packaging your JXA/osascript automation as a persistent local service. The three deployment models are: launchd agents for scheduled/recurring tasks, Automator workflows for user-triggered actions, and Apple Shortcuts for cross-app automation. Each has different permission requirements and lifecycle management.
Prerequisites
- An owned interactive macOS user session, an approved launchd label, and explicit TCC consent for the actual client path.
- Immutable versioned scripts with a tested rollback package; do not deploy from a mutable home-directory glob.
- A restricted log directory and a non-production account or folder for initial smoke tests.
Instructions
- Install a versioned artifact into a user-owned directory with restrictive permissions and validate the plist with
plutil -lint.
- Bootstrap the agent in the GUI user domain; confirm its label, executable path, and environment exactly match the reviewed release.
- Run a read-only scoped smoke test before enabling scheduled writes.
- Roll back by booting out the exact label and restoring the previously approved artifact; never leave two schedules active.
launchd Agent (Recommended for Background Tasks)
<!-- ~/Library/LaunchAgents/com.yourorg.notes-automation.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.yourorg.notes-automation</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/node</string>
<string>/Users/you/scripts/notes-sync.js</string>
</array>
<key>StartInterval</key>
<integer>3600</integer>
<key>StandardOutPath</key>
<string>/tmp/notes-automation.log</string>
<key>StandardErrorPath</key>
<string>/tmp/notes-automation-error.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
# Deploy and manage the launchd agent
cp com.yourorg.notes-automation.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.yourorg.notes-automation.plist
launchctl list | grep notes-automa
Implement access control for multi-user Apple Notes automation.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Enterprise RBAC
Overview
Apple Notes has no built-in role-based access control (RBAC). In enterprise environments with Managed Apple IDs via Apple Business Manager, administrators control Notes access through MDM (Mobile Device Management) profiles. For multi-user automation scenarios, implement access control at the automation layer using account separation, folder-based permissions, and shared folder restrictions. iCloud Shared Notes (macOS Ventura+) provide basic collaboration, but fine-grained permissions (read-only vs edit) must be enforced in your wrapper code.
Prerequisites
- An identity source, role owner, approval workflow, and audit retention policy external to Notes.app.
- A reviewed allowlist mapping stable automation identities to explicitly configured account/folder scopes.
- MDM and legal/security approval for managed-device controls; folder conventions alone are not an authorization boundary.
Instructions
- Authenticate and authorize every automation action before invoking JXA, default-deny unknown roles, accounts, folders, and operations.
- Use stable configuration identifiers rather than account or folder names in telemetry; store mappings in a protected configuration service.
- Apply least privilege: separate read, write, delete, and export approvals, and require elevated review for destructive operations.
- Audit authorization decisions and periodically test revocation; TCC consent does not replace application authorization.
Account-Based Access Control
// Apple Notes supports multiple accounts (iCloud, Gmail, On My Mac)
// Use account separation as the primary access boundary
const Notes = Application("Notes");
function getAccountByName(name) {
const account = Notes.accounts().find(a => a.name() === name);
if (!account) throw new Error(`Account not found: ${name}`);
return account;
}
// Audit all accounts and their folder structures
function auditAccounts() {
return Notes.accounts().map(a => ({
name: a.name(),
folders: a.folders().map(f => f.name()),
noteCount: a.notes().length,
}));
}
// Restrict automation to a specific account only
const ALLOWED_ACCOUNT = "iCloud";
function safeGetNotes() {
const account = getAccountByName(ALLOWED_ACCOUNT);
return account.notes();
}
Folder-Based Permission Model
// src/rbac/permissions.ts
interface FolderPermission {
folder: string;
allowedRoles: string[];
operations: ("read" | "write" | "delete")[];
}
const FOLDER_PERMISSIONS: FolderPermission[] = [
{ folder: "Public", allowedRoles: ["viewer", "editor", "admin"], operations: ["read"] },
{ folder: "Team", allowedRoles: ["editor", "admin&
Create, read, and list Apple Notes using JXA and AppleScript.
ReadWriteEditBash(osascript:*)
Apple Notes Hello World
Overview
Create, read, search, and delete Apple Notes using JXA (JavaScript for Automation) via osascript. All examples work from the command line on macOS.
Prerequisites
- Completed
apple-notes-install-auth (permissions granted)
- macOS with Notes.app
Instructions
Step 1: Create a Note
# JXA: Create a note in the default folder
osascript -l JavaScript -e '
const Notes = Application("Notes");
const defaultFolder = Notes.defaultAccount.folders[0];
const newNote = Notes.Note({
name: "Hello from Automation",
body: "<h1>Hello World</h1><p>This note was created via JXA at " + new Date().toISOString() + "</p>"
});
defaultFolder.notes.push(newNote);
newNote.id();
'
# AppleScript equivalent:
osascript -e '
tell application "Notes"
tell account "iCloud"
make new note at folder "Notes" with properties {name:"Hello AppleScript", body:"<p>Created via AppleScript</p>"}
end tell
end tell
'
Step 2: List All Notes
# List notes with title and creation date
osascript -l JavaScript -e '
const Notes = Application("Notes");
const notes = Notes.defaultAccount.notes();
notes.slice(0, 10).map(n =>
`${n.name()} | Created: ${n.creationDate().toISOString().split("T")[0]}`
).join("\n");
'
Step 3: Read a Note's Content
# Read note body (returns HTML)
osascript -l JavaScript -e '
const Notes = Application("Notes");
const notes = Notes.defaultAccount.notes();
const target = notes.find(n => n.name() === "Hello from Automation");
if (target) {
`Title: ${target.name()}\nBody: ${target.body()}\nModified: ${target.modificationDate()}`;
} else {
"Note not found";
}
'
Step 4: Search Notes
# Search by keyword in note name
osascript -l JavaScript -e '
const Notes = Application("Notes");
const query = "Hello";
const results = Notes.defaultAccount.notes().filter(n =>
n.name().toLowerCase().includes(query.toLowerCase())
);
results.map(n => n.name()).join("\n") || "No results";
'
Step 5: Create Note in Specific Folder
# Create a folder and add a note to it
osascript -l JavaScript -e '
const Notes = Application("Notes");
const account = Notes.defaultAccount;
// Create folder if it does not exist
let folder = account.folders().find(f => f.name() === "Automation");
if (!folder) {
folder = Notes.Folder({ name: "Automation" })
Incident response runbook for Apple Notes automation failures.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Incident Runbook
Overview
This runbook covers the most common Apple Notes automation failures and their resolution procedures. Unlike cloud SaaS incidents that involve API endpoints and status pages, Apple Notes incidents are local to the macOS machine: app crashes, TCC permission revocations, iCloud sync failures, and database corruption. Each incident section follows a detect-diagnose-fix-verify structure. Keep this runbook accessible on any machine running Notes automation.
Prerequisites
- A named incident owner, an approved maintenance window for recovery actions, and a verified backup status.
- A redacted diagnostic location with restricted access; notes, account names, and raw unified logs can contain sensitive data.
- A documented escalation route to Apple or device management for sync, TCC, and storage failures.
Instructions
- Stop automated writes first and preserve the last successful cursor or operation ledger.
- Collect minimal, redacted diagnostics and classify the incident before restarting applications or services.
- Use supported UI, MDM, or vendor recovery paths for permissions, iCloud, and storage; do not modify TCC or Notes databases directly.
- Validate recovery with a scoped read-only check, reconcile pending mutations, and obtain owner approval before resuming writes.
Severity Levels
| Severity |
Description |
Example |
Response Time |
| P1 |
All automation blocked |
TCC permissions revoked, Notes.app won't launch |
Immediate |
| P2 |
Data inconsistency |
iCloud sync stuck, notes missing |
Within 1 hour |
| P3 |
Degraded performance |
Slow operations, intermittent timeouts |
Within 4 hours |
| P4 |
Cosmetic/minor |
Log warnings, non-critical script errors |
Next business day |
Incident 1: Notes.app Crash During Automation
# DETECT: Check if Notes is running
pgrep -x Notes > /dev/null && echo "Notes: running" || echo "Notes: NOT RUNNING"
# DIAGNOSE: Check crash logs
ls -lt ~/Library/Logs/DiagnosticReports/Notes* 2>/dev/null | head -3
# FIX: Restart Notes with stabilization delay
killall Notes 2>/dev/null
sleep 3
open -a Notes
sleep 5 # Wait for full launch and iCloud handshake
# VERIFY: Confirm access is restored
osascript -l JavaScript -e 'Application("Notes").defaultAccount.notes.length'
Incident 2: iCloud Sync Stuck
# DETECT: Compare note count with expected (from last known good)
CURRENT=$(osascript -l JavaScript -e 'Application("Notes").defaultAccount.notes.length' 2>/dev/null)
echo "Cu
Set up macOS automation access for Apple Notes via AppleScript, JXA, and Shortcuts.
ReadWriteEditBash(osascript:*)Bash(defaults:*)Grep
Apple Notes Install & Auth
Overview
Apple Notes has no REST API. Automation uses macOS scripting technologies: AppleScript, JavaScript for Automation (JXA), Shortcuts, and the osascript command-line tool. No SDK to install — but you need macOS accessibility permissions.
Prerequisites
- macOS 13+ (Ventura or later recommended)
- Terminal app or iTerm2
- System Preferences > Privacy & Security > Automation permissions
Instructions
Step 1: Grant Automation Permissions
# macOS requires explicit permission for scripts to control Notes.app
# The first time you run an osascript command targeting Notes, macOS will prompt.
# You can also pre-grant in: System Preferences > Privacy & Security > Automation
# Test basic Notes access (will trigger permission prompt)
osascript -e 'tell application "Notes" to get name of every note in default account'
Step 2: Verify JXA (JavaScript for Automation) Access
# JXA is the modern alternative to AppleScript
# Run JavaScript via osascript with -l JavaScript flag
osascript -l JavaScript -e '
const Notes = Application("Notes");
Notes.includeStandardAdditions = true;
const noteCount = Notes.defaultAccount.notes.length;
`Apple Notes accessible: ${noteCount} notes found`;
'
Step 3: Create a Wrapper Script
#!/bin/bash
# scripts/notes-cli.sh — Wrapper for common Apple Notes operations
case "$1" in
count)
osascript -l JavaScript -e '
const Notes = Application("Notes");
Notes.defaultAccount.notes.length;
'
;;
list)
osascript -l JavaScript -e '
const Notes = Application("Notes");
const notes = Notes.defaultAccount.notes();
notes.slice(0, 20).map(n => `${n.id()} | ${n.name()}`).join("\n");
'
;;
folders)
osascript -l JavaScript -e '
const Notes = Application("Notes");
Notes.defaultAccount.folders().map(f => f.name()).join("\n");
'
;;
*)
echo "Usage: notes-cli.sh {count|list|folders}"
;;
esac
Step 4: Verify Shortcuts Integration
# Apple Shortcuts can also interact with Notes
# Check available shortcuts
shortcuts list | grep -i note
# Run a shortcut that creates a note
shortcuts run "Create Note" --input-path /dev/stdin <<< "Test content"
Automation Technologies
| Technology |
Language |
Best For |
Docs |
| AppleScript |
AppleScript |
Simple operations |
Apple Scripting Guide |
| JXA |
JavaScript |
Complex logic, JSON handl
Set up local development workflow for Apple Notes automation with JXA hot reload.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Local Dev Loop
Overview
Iterative development workflow for Apple Notes JXA scripts with file watching and test helpers.
Prerequisites
- A local macOS development machine and a test-only
On My Mac folder, or a fully mocked Notes adapter.
- Synthetic test fixtures with no production note content, account names, or credentials.
- A source-controlled allowlist of scripts eligible for local execution.
Instructions
Step 1: Project Setup
mkdir apple-notes-automation && cd apple-notes-automation
npm init -y
npm install -D chokidar tsx typescript
Step 2: JXA Runner with Hot Reload
// src/dev/watch-runner.ts
import { watch } from "chokidar";
import { execSync } from "child_process";
watch("scripts/*.js", { ignoreInitial: true }).on("change", (path) => {
console.log(`Changed: ${path} — running...`);
try {
const output = execSync(`osascript -l JavaScript "${path}"`, { encoding: "utf8" });
console.log(output);
} catch (err: any) {
console.error(err.stderr);
}
});
console.log("Watching scripts/*.js for changes...");
Step 3: Test Helper
// src/dev/test-notes.ts
import { execSync } from "child_process";
function runJxa(script: string): string {
return execSync(`osascript -l JavaScript -e '${script}'`, { encoding: "utf8" }).trim();
}
function getNoteCount(): number {
return parseInt(runJxa("Application(\"Notes\").defaultAccount.notes.length"));
}
function createTestNote(title: string): string {
return runJxa(`
const Notes = Application("Notes");
const note = Notes.Note({name: "${title}", body: "<p>Test</p>"});
Notes.defaultAccount.folders[0].notes.push(note);
note.id();
`);
}
export { runJxa, getNoteCount, createTestNote };
Step 4: Dev Scripts
{
"scripts": {
"dev": "tsx src/dev/watch-runner.ts",
"test:notes": "tsx src/dev/test-notes.ts"
}
}
Output
- Hot-reload JXA development with file watching
- Test helpers for note CRUD operations
- Iterative script development workflow
Error Handling
Stop the watcher when a script fails and surface only the exit status plus a redacted error category. Never hot-run an edited script against a production or synchronized default account. If a test mutation is required, use the designated test folder and reconcile it before the next run.
Examples
Run unit tests against a mock client while editing. When a JXA smoke test is necessary, point it at the designated local test f
Migrate notes between Apple Notes, Obsidian, Notion, and other platforms.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Migration Deep Dive
Overview
Migrating to or from Apple Notes requires understanding that Notes stores content as proprietary HTML with no REST API for bulk operations. All automation goes through JXA/osascript on a local Mac. This guide covers the four most common migration paths with production-tested scripts. Key challenges include: HTML-to-Markdown conversion fidelity, attachment extraction limitations (JXA cannot export binary attachment data directly), and iCloud sync delays that affect timing of bulk imports.
Prerequisites
- Written scope, migration owner, encrypted backup, rollback decision, and retention policy for both source and destination.
- A synthetic pilot corpus that includes formatting and attachment edge cases but contains no production data.
- A durable manifest keyed by source identifier and a target environment/folder that has been explicitly approved.
Instructions
- Run export, conversion, and import as separate, inspectable phases; do not stream unreviewed note bodies into a destination.
- Sanitize HTML/Markdown and filenames, encrypt intermediate artifacts, and keep content out of shell arguments and logs.
- Pilot a small batch, reconcile source and destination manifests, then obtain owner approval before each larger batch.
- Make imports idempotent and stop on a timeout, conflict, or attachment-fidelity gap; do not retry blindly.
Migration Paths
| From |
To |
Method |
Attachments |
| Apple Notes |
Obsidian |
JXA export HTML → convert to Markdown → vault |
Manual via Shortcuts |
| Apple Notes |
Notion |
JXA export JSON → Notion API import |
Re-upload required |
| Obsidian |
Apple Notes |
Read .md → convert to HTML → JXA create |
Not supported via JXA |
| Evernote |
Apple Notes |
File > Import from Evernote (built-in) |
Preserved automatically |
| OneNote |
Apple Notes |
Export to .enex → Import from Evernote |
Partial preservation |
Step 1: Pre-Migration Backup
#!/bin/bash
# Always back up before migration
BACKUP_DIR="$HOME/notes-backup-$(date +%Y%m%d-%H%M)"
mkdir -p "$BACKUP_DIR"
osascript -l JavaScript -e '
const Notes = Application("Notes");
const data = Notes.defaultAccount.notes().map(n => ({
id: n.id(), title: n.name(), body: n.body(),
folder: n.container().name(),
created: n.creationDate().toISOString(),
modified: n.modificationDate().toISOString(),
attachments: n.attachments().length
}));
JSON.stringify(data, null, 2);
' > "$BACKUP_DIR/full-export.json"
echo "Backed up $(jq length "$BACKU
Configure Apple Notes automation for multiple accounts and environments.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Multi-Environment Setup
Overview
Apple Notes supports multiple accounts simultaneously: iCloud (default), Gmail/Yahoo/AOL via IMAP, Exchange, and the local "On My Mac" account. Each account has isolated folders and notes, making accounts the natural boundary for environment separation. Use this to separate personal vs work notes, production vs development data, or synced vs local-only content. The "On My Mac" account is especially useful for development and testing because it never syncs to iCloud, so experiments stay local.
Prerequisites
- A written environment map that names the intended account and folder by stable local configuration, not a broad account default.
- Separate test data for development and staging; iCloud folders alone are not an access-control boundary.
- An approval path for creating accounts or folders, especially in managed or shared environments.
Instructions
- Resolve the selected environment from an allowlist and fail if it is not explicitly configured.
- Verify the expected account and folder exist before a write; do not silently create missing production folders.
- Keep development local where possible, and require an explicit operator choice before a job touches a synchronized account.
- Log only the environment label and opaque configuration version, never account names, note titles, or bodies.
Account Discovery
# List all configured Notes accounts
osascript -l JavaScript -e '
const Notes = Application("Notes");
Notes.accounts().map(a =>
a.name() + " — " + a.notes().length + " notes, " +
a.folders().map(f => f.name()).join(", ")
).join("\n");
'
Environment-Based Configuration
// src/config/environments.ts
interface NotesEnvConfig {
accountName: string;
defaultFolder: string;
autoSync: boolean;
description: string;
}
const ENVIRONMENTS: Record<string, NotesEnvConfig> = {
production: {
accountName: "iCloud",
defaultFolder: "Production",
autoSync: true,
description: "Live notes synced across all devices via iCloud",
},
staging: {
accountName: "iCloud",
defaultFolder: "Staging",
autoSync: true,
description: "Test notes visible on other devices for QA",
},
development: {
accountName: "On My Mac",
defaultFolder: "Dev",
autoSync: false,
description: "Local-only notes for development and testing",
},
};
function getEnv(): string {
return process.env.NOTES_ENV || "development";
}
Account-Scoped Operations
// JXA wrapper that enforces account isolation
const Notes = Application(&quo
Monitor Apple Notes automation health and performance metrics.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Observability
Overview
Apple Notes has no built-in metrics API or health endpoint. Observability must be built from the outside: polling note counts and folder states via JXA, monitoring iCloud sync daemon health, tracking osascript response latency, and watching system logs for Notes-related errors. This guide sets up a lightweight monitoring stack using bash scripts, structured JSON logs, and macOS notifications for alerting. For persistent monitoring, deploy the health check as a launchd agent that runs on a schedule.
Prerequisites
- A job-owned, permission-restricted log directory with rotation and retention limits.
- A monitoring scope limited to the approved account/folder; counts are sensitive operational metadata and must not be sent to broad telemetry.
- A defined alert owner and a sustained-failure threshold so transient iCloud or TCC conditions do not trigger unsafe remediation.
Instructions
- Collect only health status, bounded latency, and coarse scoped counts required for the alert decision.
- Sanitize shell output before emitting JSON; do not interpolate account names, note titles, bodies, or raw errors into notifications.
- Alert after the agreed consecutive failure threshold and link to the incident runbook.
- Rotate logs and review access periodically; monitoring must never restart iCloud processes or modify Notes state.
Health Check Script
#!/bin/bash
# scripts/notes-health-check.sh — Deploy via launchd (every 5 minutes)
LOG_FILE="${NOTES_LOG_DIR:-/tmp}/notes-health.jsonl"
timestamp=$(date -Iseconds)
notes_running=$(pgrep -x Notes > /dev/null && echo "true" || echo "false")
# Measure JXA latency
start_ms=$(($(date +%s%N)/1000000))
note_count=$(osascript -l JavaScript -e 'Application("Notes").defaultAccount.notes.length' 2>/dev/null || echo "-1")
folder_count=$(osascript -l JavaScript -e 'Application("Notes").defaultAccount.folders.length' 2>/dev/null || echo "-1")
account_count=$(osascript -l JavaScript -e 'Application("Notes").accounts().length' 2>/dev/null || echo "-1")
end_ms=$(($(date +%s%N)/1000000))
latency_ms=$((end_ms - start_ms))
# iCloud sync daemon status
bird_running=$(pgrep -x bird > /dev/null && echo "true" || echo "false")
cloudd_running=$(pgrep -x cloudd > /dev/null && echo "true" || echo "false")
# Determine health
healthy="true"
[ "$notes_running" = "false" ] && healthy="false"
[ "$note_count" = "-1" ] && healthy="false"
[ "$latency_ms" -gt 10000 ] && healthy="false"
echo "{\"ts\":\"$timestamp\",\"running\"
Optimize Apple Notes automation performance for large note collections.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Performance Tuning
Overview
Apple Notes automation performance degrades linearly with note count because JXA loads all note objects into memory when you access a collection. A vault with 10,000+ notes can take 30+ seconds for a simple list operation. The primary bottleneck is the Apple Events bridge between your script and Notes.app — every property access (name, body, date) is a separate IPC call. This guide covers caching strategies, incremental sync, batch optimization, and architectural patterns to keep automation responsive at scale.
Prerequisites
- A scoped workload baseline, performance budget, and a non-production corpus for tests.
- A private encrypted cache with retention, redaction, and explicit access controls; a cache is a duplicate of sensitive notes.
- A feature flag and rollback plan for polling interval, cache, or batching changes.
Instructions
- Measure a bounded read-only workload before changing timeouts, concurrency, or poll intervals.
- Minimize fields and property calls; never cache a body when an opaque identifier or timestamp is sufficient.
- Keep cache updates idempotent, encrypted, and scoped; reconcile change cursors before writing a new cursor.
- Roll back a tuning change if latency, sync behavior, or data-reconciliation evidence regresses.
Performance Benchmarks
| Operation |
100 notes |
1,000 notes |
10,000 notes |
| List all (names only) |
~0.5s |
~3s |
~30s |
Search by name (.whose()) |
~0.3s |
~2s |
~20s |
| Full-text search (body scan) |
~1s |
~8s |
~80s |
| Create single note |
~0.2s |
~0.2s |
~0.2s |
| Export all to JSON |
~1s |
~10s |
~100s |
Count notes only (.length) |
~0.1s |
~0.3s |
~1s |
Strategy 1: Minimize Property Access
// BAD: Each property access is a separate Apple Event IPC call
const Notes = Application("Notes");
const allNotes = Notes.defaultAccount.notes();
allNotes.forEach(n => {
console.log(n.name()); // IPC call 1
console.log(n.body()); // IPC call 2
console.log(n.modificationDate()); // IPC call 3
});
// With 1000 notes = 3000 IPC calls
// GOOD: Batch extract in a single JXA evaluation
const data = Notes.defaultAccount.notes().map(n => ({
title: n.name(),
modified: n.modificationDate().toISOString(),
}));
// Single JXA evaluation, much faster for bulk reads
Strategy 2: Local SQLite Cache
#!/bin/bash
# Export notes to SQLite for fast local queries
DB="$HOME/.notes-cache.db"
sqlite3 "$DB&quo
Production checklist for Apple Notes automation deployments.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Production Checklist
Overview
Before deploying Apple Notes automation to a production macOS machine, validate every dependency: TCC permissions, iCloud sync health, Notes.app availability, error handling robustness, and data security. Unlike cloud services where deployment is a push, Apple Notes automation requires physical or remote access to a Mac with a logged-in user session. This checklist ensures nothing is missed before going live.
Prerequisites
- An owned, interactive macOS host with the target account and an approved automation consent path.
- A documented backup and restore exercise for the specific notes and folders the automation may change.
- A non-production test folder or account for any write-path verification; production validation must be read-only by default.
Instructions
- Complete the checklist on the exact host and user context that will run the automation.
- Verify only the account and folders explicitly in scope; do not enumerate or export unrelated notes.
- Treat a failed authorization, sync, or backup check as a deployment stop, not a warning.
- Run a write-path test only in the designated test folder, confirm cleanup manually, and retain no note body in logs.
Pre-Deployment Checklist
Permissions and Access
- [ ] TCC automation permission granted (System Settings > Privacy > Automation)
- [ ] Permission tested from the exact context that will run in production (Terminal, launchd, etc.)
- [ ] Full Disk Access granted if reading Notes database directly (not recommended)
- [ ] Script runs without interactive prompts (no "Allow" dialogs left)
Application Configuration
- [ ] Notes.app configured to launch at login (System Settings > General > Login Items)
- [ ] Target Apple ID / iCloud account signed in and syncing
- [ ] "On My Mac" account enabled if local storage is needed
- [ ] Correct default account set for automation scripts
Data and Sync
- [ ] iCloud sync verified working (create note on Mac, verify on iPhone)
- [ ] Backup strategy documented (JSON export on schedule)
- [ ] Exported data files have restricted permissions (
chmod 600)
- [ ] No sensitive data written to logs or temp files
Reliability
- [ ] Error handling for all AppleEvent failure codes (-1743, -1712, -609, -1728)
- [ ] Retry logic with exponential backoff for transient failures
- [ ] Write operations throttled (max 1 per second for iCloud sync)
- [ ] Health check script deployed and running on schedule
- [ ] Alerting configured for automation failures (macOS notification or webhook)
Compatibility
- [ ] Script tested on target macOS version (
sw_vers)
Handle Apple Notes automation rate limits and iCloud sync throttling.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Rate Limits
Overview
Apple Notes has no formal API rate limits like cloud services do. However, there are practical throughput limits imposed by three systems: the Apple Events IPC bridge (osascript to Notes.app), the iCloud sync daemon (bird/cloudd) that must process each write, and the Notes.app SQLite database that handles concurrent access. Exceeding these practical limits causes timeouts (-1712), sync lag, or data loss when writes outpace iCloud's upload buffer. This guide documents safe operation rates and provides throttling patterns.
Prerequisites
- A tested Notes adapter that keeps note data out of command strings and logs.
- A bounded queue with an idempotency key or durable operation ledger for each write.
- An approved maintenance window and backup for bulk mutations; the rates below are conservative starting points, not a vendor guarantee.
Instructions
- Begin below the listed rates and increase only after observing successful sync on every required device.
- Serialize writes and deletes; do not retry a timed-out mutation until a durable idempotency check determines whether it succeeded.
- Pause the queue and escalate if timeout, sync lag, or duplicate detection crosses the service threshold.
- Do not terminate iCloud processes to recover throughput; preserve evidence and use normal OS recovery procedures.
Practical Rate Limits
| Operation |
Safe Rate |
Bottleneck |
Exceeding Limit |
| Create note |
1/second |
iCloud sync buffer |
Sync lag; notes missing on other devices |
| Read note (name/body) |
10/second |
Apple Events IPC |
-1712 timeout errors |
Search (.whose()) |
2/second |
Notes.app indexer |
UI freeze; timeout |
| Move note between folders |
1/second |
iCloud + local DB |
Folder state inconsistency |
| Delete note |
1/second |
iCloud delete propagation |
Deleted notes reappear |
| Bulk list (all notes) |
1/10 seconds |
Memory + IPC |
Process killed by macOS |
| Attachment operations |
1/5 seconds |
File I/O + sync |
Corrupt or missing attachments |
Throttled Operation Queue
// src/rate-limit/throttle.ts
import { execSync } from "child_process";
interface ThrottleConfig {
minDelayMs: number;
maxRetries: number;
backoffMultiplier: number;
}
const THROTTLE_CONFIGS: Record<string, ThrottleConfig> = {
read: { minDelayMs: 100, maxRetries: 3, backoffMultiplier: 2 },
write: { minDelayMs: 1000, maxRetries: 5, backoffMultiplier: 2 },
delete: { m
Reference architecture for Apple Notes automation systems.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Reference Architecture
Overview
Apple Notes automation systems are fundamentally different from cloud SaaS integrations. There is no REST API, no server-side SDK, and no webhook infrastructure. Everything runs locally on macOS through the Apple Events IPC bridge. This reference architecture defines the standard layered approach: a Node.js application layer that calls JXA scripts via osascript, a local SQLite cache for fast queries, a change detection poller for event-driven workflows, and optional Shortcuts integration for cross-app automation.
Prerequisites
- An owned interactive macOS host, exact client TCC consent, and a declared account/folder scope.
- A reviewed local-only service boundary, encrypted data stores, and an incident/rollback owner.
- Mocked tests for all application logic; device integration tests run only on a protected self-hosted Mac.
Instructions
- Place authorization, input validation, idempotency, and audit logging above the JXA adapter; the adapter should receive only validated scoped commands.
- Bind any local service to loopback by default and require an authenticated, approved transport for remote administration.
- Treat cache and event data as sensitive replicas: minimize fields, encrypt at rest, restrict access, rotate/delete under policy, and never read NoteStore directly.
- Separate liveness from readiness; pause mutations when authorization, reconciliation, or sync health is uncertain.
System Architecture
┌─────────────────────────────────────────────────────┐
│ macOS Machine │
│ │
│ ┌──────────┐ ┌───────────┐ ┌────────────────┐ │
│ │ Your App │──▶│ osascript │──▶│ Notes.app │ │
│ │ (Node.js)│ │ (JXA) │ │ (local DB) │ │
│ └────┬─────┘ └───────────┘ └───────┬────────┘ │
│ │ │ │
│ ┌────▼─────┐ ┌───────────┐ ┌───────▼────────┐ │
│ │ SQLite │ │ Shortcuts │ │ iCloud Sync │ │
│ │ Cache │ │ Automations│ │ (bird/cloudd) │ │
│ └──────────┘ └───────────┘ └────────────────┘ │
│ │ │ │
│ ┌────▼─────┐ ┌────────▼───────┐ │
│ │ Poller / │ │ Other Apple │ │
│ │ FSEvents │ │ Devices │ │
│ └──────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────┘
Project Structure
apple-notes-automation/
├── src/
│ ├── notes-client.ts # JXA wrapper class (osascript calls)
│ ├── cache.ts # SQLite cache layer
│ ├── templates/ # Note templates (HTML fragments)
│ ├── export/ # Export to MD/JSON/SQLite/CSV
│ ├── events/
Apply production-ready patterns for Apple Notes JXA/AppleScript automation.
ReadWriteEditBash(osascript:*)Grep
Apple Notes SDK Patterns
Overview
Production patterns for Apple Notes automation: JXA wrapper class, error handling, batch operations, and cross-account support.
Prerequisites
- A scoped account/folder configuration resolved outside the JXA source string.
- A safe process invocation boundary that passes source and data without shell interpolation.
- Durable idempotency tracking for writes and a synthetic local test corpus.
Instructions
- Treat names, bodies, queries, and folder identifiers as data—not template fragments or shell arguments.
- Resolve only explicitly configured accounts and folders, and fail if the target is absent rather than creating it implicitly.
- Keep list and search results scoped and minimize returned fields; never log note bodies by default.
- Serialize mutations, record an opaque idempotency key before the call, and reconcile timeouts before retrying.
Procedure
Step 1: JXA Client Wrapper (Node.js)
// src/notes-client.ts
import { execSync } from "child_process";
class AppleNotesClient {
private runJxa(script: string): string {
const escaped = script.replace(/'/g, "\\'");
return execSync(`osascript -l JavaScript -e '${escaped}'`, {
encoding: "utf8",
timeout: 30000,
}).trim();
}
listNotes(folder?: string, limit: number = 50): Array<{ id: string; title: string; modified: string }> {
const script = folder
? `const Notes = Application("Notes"); const f = Notes.defaultAccount.folders().find(f => f.name() === "${folder}"); (f ? f.notes() : []).slice(0, ${limit}).map(n => JSON.stringify({id: n.id(), title: n.name(), modified: n.modificationDate().toISOString()})).join("\\n")`
: `const Notes = Application("Notes"); Notes.defaultAccount.notes().slice(0, ${limit}).map(n => JSON.stringify({id: n.id(), title: n.name(), modified: n.modificationDate().toISOString()})).join("\\n")`;
return this.runJxa(script).split("\n").filter(Boolean).map(l => JSON.parse(l));
}
createNote(title: string, body: string, folder?: string): string {
const folderPart = folder
? `let f = account.folders().find(f => f.name() === "${folder}"); if (!f) { f = Notes.Folder({name: "${folder}"}); account.folders.push(f); }`
: "let f = account.folders[0];";
return this.runJxa(`
const Notes = Application("Notes");
const account = Notes.defaultAccount;
${folderPart}
const note = Notes.Note({name: ${JSON.stringify(title)}, body: ${JSON.stringify(body)}});
f.notes.push(note);
note.id();
`);
}
searchNotes(query: string): Array<{ title: string; folder: string }> {
const result = this.runJxa(`
const Notes = Appli
Apply security best practices for Apple Notes automation scripts.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Security Basics
Overview
Apple Notes security involves three layers: macOS TCC (Transparency, Consent, and Control) which gates which apps can send Apple Events to Notes.app, the macOS sandbox that prevents direct database access, and iCloud encryption that protects notes in transit and at rest. For automation scripts, the primary security concerns are: preventing unauthorized Apple Events access, securing exported note data, avoiding credential leakage in scripts, and understanding the difference between standard and end-to-end encrypted (locked) notes.
Prerequisites
- An inventory of the invoking app, macOS user, account scope, and required operation class.
- A documented incident owner and revocation procedure for automation permissions and exported artifacts.
- A test account for exercising write paths; never use a live note as a security test fixture.
Instructions
- Grant Apple Events consent only through System Settings or an approved MDM profile for the exact client application.
- Keep automation local and reject network-provided note content, script fragments, and shell arguments.
- Encrypt backups, restrict access before writing, and delete temporary artifacts through a verified retention process.
- Skip locked or unreadable notes, record a redacted failure, and require a user to handle them in Notes.app.
Security Checklist
- [ ] Scripts run only locally (never expose osascript to network input)
- [ ] No note content written to log files (may contain PII or secrets)
- [ ] TCC permissions scoped to specific apps only (not blanket approval)
- [ ] Exported notes stored with restrictive permissions (
chmod 600)
- [ ] iCloud account uses two-factor authentication
- [ ] Automation scripts do not hardcode note content or search terms
- [ ] Temporary files cleaned up after processing (
trap on exit)
- [ ] Locked (encrypted) notes handled separately (cannot be read via JXA)
TCC Permission Management
# View which apps have automation access in System Settings:
# System Settings > Privacy & Security > Automation
open "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation"
Safe Data Export Pattern
#!/bin/bash
# Secure export with cleanup on exit
EXPORT_FILE=$(mktemp /tmp/notes-export-XXXXXX.json)
trap 'rm -f "$EXPORT_FILE"' EXIT
# Export with restricted permissions from the start
umask 077
osascript -l JavaScript -e '
const Notes = Application("Notes");
JSON.stringify(Notes.defaultAccount.notes().map(n => ({
title: n.name(),
body: n.plaintext(),
folder: n.container().name()
})));
' > "$EXPORT_FILE"
echo "Exported to $EXPORT
Migrate Apple Notes automation scripts between macOS versions.
ReadWriteEditBash(osascript:*)Grep
Apple Notes Upgrade & Migration
Overview
Each macOS major release can change Apple Notes capabilities, JXA API behavior, and the underlying NoteStore database schema. Automation scripts that work on Ventura may fail on Sonoma due to new properties, changed Apple Events handling, or TCC permission resets. This guide covers version-specific changes, pre-upgrade backup procedures, post-upgrade validation, and a compatibility matrix for JXA features across macOS versions.
Prerequisites
- A supported-device inventory, a tested encrypted backup, and a documented rollback/incident owner.
- A non-production test account or folder for optional mutation verification; routine post-upgrade validation is read-only.
- Reviewed release notes for the actual macOS version, rather than relying solely on the illustrative compatibility table.
Instructions
- Pause scheduled writes before the upgrade and capture a redacted pre-upgrade receipt with scope, count, checksum, and automation version.
- After the upgrade, review consent in System Settings or MDM for the exact client and run only scoped read-only checks first.
- Compare bounded reconciliation data to the pre-upgrade receipt; investigate discrepancies without changing Notes storage.
- Re-enable writes only after an owner accepts the validation record; perform any write test in the dedicated test folder.
macOS Version Compatibility Matrix
| macOS Version |
Notes Features Added |
JXA Impact |
Breaking Changes |
| Monterey (12) |
Quick Notes, #tags in body |
No new JXA properties |
None |
| Ventura (13) |
Shared notes, smart folders |
Sharing not exposed in JXA |
TCC changes; re-prompt required |
| Sonoma (14) |
Tags as first-class, link notes |
Tag properties partially accessible |
Smart folder API changed |
| Sequoia (15) |
Math expressions, audio recording |
New content types in body HTML |
Apple Events timeout behavior changed |
Pre-Upgrade Backup
#!/bin/bash
# Run BEFORE upgrading macOS
BACKUP_DIR="$HOME/notes-pre-upgrade-$(sw_vers -productVersion)-$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
echo "Backing up Apple Notes before macOS upgrade..."
echo "Current macOS: $(sw_vers -productVersion)"
# Full export with metadata
osascript -l JavaScript -e '
const Notes = Application("Notes");
const data = Notes.accounts().map(a => ({
account: a.name(),
notes: a.notes().map(n => ({
id: n.id(), title: n.name(), body: n.body(),
folder: n.container().name(),
created: n.creationDate().toISOString(),
modified: n.modificationDate().toISOString
Monitor Apple Notes changes using file system events and Shortcuts triggers.
ReadWriteEditBash(osascript:*)Grep
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.
Prerequisites
- An approved automation identity with read-only access to the intended account and folders.
- Durable event storage and deduplication before any downstream side effect.
- A scoped data policy: event records should use opaque note identifiers or salted hashes, not titles or note bodies.
Instructions
- Use polling as the authoritative signal and regard FSEvents and Shortcuts as hints that trigger a new scoped poll.
- Persist a cursor or snapshot only after a complete successful poll; retain the prior cursor on failure.
- Deduplicate events by note identifier, change version, and handler before invoking exports, notifications, or other mutations.
- Debounce file-system activity and never read, modify, or write Apple Notes' private SQLite files directly.
Approach 1: JXA Polling (Recommended)
// src/events/notes-watcher.ts
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.has(n.id)).map(n => n.title);
const modified = current.filter(n =>
lastSnapshot.has(n.id) && lastSnapshot.get(n.id) !== n.modified
).map(n => n.title);
const deleted = [...lastSnapshot.keys()].filter(id => !currentMap.has(id));
lastSnapshot = currentMap;
return { added, modified, deleted };
}
// Poll every 60 seconds
setInterval(() => {
const changes = detectChanges();
if (changes.added.length || changes.modified.length || changes.deleted.length) {
console.log("Changes detected:", JSON.stringify(changes));
// Trigger downstream actions: export, sync, notify
}
}, 60000);
Approach 2: FSEvents on Notes Database
#!/bin/bash
Ready to use apple-notes-pack?
|
|