Migrating Apple Notes to Notion: An Accidental Dive into macOS Sandboxing and Protobuf
I recently wanted to copy all my Apple Notes into Notion. Not migrate — I wanted to keep my Apple Notes intact and just have a copy in Notion. What seemed like a simple task turned into a fascinating journey through macOS security, SQLite databases, and Protocol Buffers.
Finding the Apple Notes Database
Apple Notes stores everything in a SQLite database:
# iCloud-synced notes
~/Library/Group Containers/group.com.apple.notes/NoteStore.sqlite
# Local-only notes
~/Library/Containers/com.apple.Notes/Data/Library/Notes/NotesV7.storedata
The First Wall: macOS TCC
When I tried to access the database:
$ cp ~/Library/Group\ Containers/group.com.apple.notes/NoteStore.sqlite /tmp/
cp: Operation not permitted
This wasn’t a regular permissions error. macOS has TCC (Transparency, Consent, and Control) — a privacy protection system that restricts access to sensitive user data, even for processes running as your user.
The Sandboxing Layers
macOS implements multiple security layers:
- Traditional Unix permissions — The classic user/group/other model
- TCC — Privacy controls for sensitive data (contacts, photos, notes)
- App Sandbox — Restricts what App Store apps can access
- SIP — Protects system files even from root
For Apple Notes, TCC is the gatekeeper. To bypass it, add your terminal app to System Settings → Privacy & Security → Full Disk Access.
First Attempt: Heuristic Text Extraction
The note content lives in a ZDATA column as binary blobs. My first approach was naive — decompress gzip and filter readable characters:
text = ''.join(chr(b) for b in decompressed if 32 <= b < 127)
This produced garbage with protobuf artifacts mixed into the text. Not usable.
The Real Solution: Protocol Buffers
Apple Notes uses Protocol Buffers (protobuf) to serialize note content. You need the schema to decode it properly.
After researching, I found the schema from reverse engineering efforts (notably apple_cloud_notes_parser).
The key insight: note text lives at NoteStoreProto.document.note.note_text.
import gzip
import notestore_pb2
def extract_note_text(data):
decompressed = gzip.decompress(data)
note_proto = notestore_pb2.NoteStoreProto()
note_proto.ParseFromString(decompressed)
return note_proto.document.note.note_text.strip()
Clean, perfectly formatted text — exactly as it appears in Apple Notes.
The Code
I’ve published the complete solution as gists:
- Migration Script — Full Python script that reads Apple Notes and creates Notion pages
- Protobuf Schema — The
.protofile for decoding Apple Notes content
To use:
# Install dependencies
pip install protobuf requests
# Compile the protobuf schema
protoc --python_out=. notestore.proto
# Set your credentials
export NOTION_TOKEN="your-notion-integration-token"
export NOTION_PARENT_PAGE_ID="your-target-page-id"
# Run the migration
python migrate_notes.py --dry-run # Preview first
python migrate_notes.py # Run for real
Results
After running the migration:
- 1038 notes successfully copied to Notion
- 1 note failed (likely corrupted)
- Original Apple Notes remained untouched
Lessons Learned
-
macOS security is multi-layered — Understanding TCC vs. traditional permissions saves debugging time.
-
Binary formats require proper decoding — Heuristic approaches rarely work. Find the actual format specification.
-
Protocol Buffers are everywhere — Apple uses them extensively. Learning protobuf opens up many reverse engineering possibilities.
-
Copy databases before reading — SQLite files can be locked by the owning app. Always work with a copy.
-
Rate limiting matters — Notion’s API allows 3 requests/second. Respecting this prevents failures.
What’s Next?
The current script handles text only. To extend it:
- Handle attachments — Images are stored separately and referenced via
AttachmentInfo - Preserve formatting — Use
AttributeRunto apply bold, italic, links - Export checklists — The
Checklistmessage contains todo state
The protobuf schema in the gist includes these message types if you want to build on it. Happy migrating!