Migrating Apple Notes to Notion: An Accidental Dive into macOS Sandboxing and Protobuf

· 4 min read ·
#macos#python#reverse-engineering#notion

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:

  1. Traditional Unix permissions — The classic user/group/other model
  2. TCC — Privacy controls for sensitive data (contacts, photos, notes)
  3. App Sandbox — Restricts what App Store apps can access
  4. 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 .proto file 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

  1. macOS security is multi-layered — Understanding TCC vs. traditional permissions saves debugging time.

  2. Binary formats require proper decoding — Heuristic approaches rarely work. Find the actual format specification.

  3. Protocol Buffers are everywhere — Apple uses them extensively. Learning protobuf opens up many reverse engineering possibilities.

  4. Copy databases before reading — SQLite files can be locked by the owning app. Always work with a copy.

  5. 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 AttributeRun to apply bold, italic, links
  • Export checklists — The Checklist message contains todo state

The protobuf schema in the gist includes these message types if you want to build on it. Happy migrating!