Export Apple Notes to SQLite.
This tool extracts notes and folders from the macOS Notes app using AppleScript and writes them to a SQLite database. It defaults to incremental sync, and supports forced full exports, filtered exports, and optional delete-missing behavior.
- macOS with the Notes app
- Python 3.7+
uv(recommended for development and testing)
From source:
pip install -e .Or with uv:
uv pip install -e .Export all notes to notes.db:
apple-notes-to-sqlite notes.dbPrint notes as newline-delimited JSON to stdout:
apple-notes-to-sqlite --dumpCreate the database schema only:
apple-notes-to-sqlite notes.db --schemaThree tables are created (if missing):
folders:id,long_id,name,parentnotes:id,created,updated,folder,title,bodysync_state:key,value(storeslast_sync)
folder in notes is a foreign key to folders.id.
--stop-after INTEGER Stop after this many notes
--dump Output notes to standard output
--schema Create database schema and exit
--full, --recreate Force a full scan (disable incremental fetching for this run)
--sync-delete-missing Delete notes missing from this run (scope aware of --folder)
--folder TEXT Only export notes from this folder (by path, name, or long_id)
--help Show this message and exit
Outputs notes as newline-delimited JSON. No database is created or modified.
Creates the folders and notes tables and exits. This is useful when you want to inspect the schema or pre-create the DB before a later run.
Limits the export to a specific folder. You can pass:
- A folder name (exact match)
- A folder path like
Parent/Child - A folder long_id
Notes in the selected folder and its descendants are included. The folder table includes the required ancestry so foreign keys can be maintained.
Incremental sync is enabled by default:
- Notes are still extracted from Notes, but DB updates are skipped for any note whose
updatedtimestamp has not changed. - After a successful full sync, the tool records a
last_synctimestamp in the database. - On subsequent runs, only notes modified after
last_syncare fetched from Notes, which makes repeated runs much faster.
Forces a full scan for that run (disables incremental fetching). This is useful if you want to rebuild from source regardless of last_sync.
Deletes notes from the target DB that were not seen in the current run.
Important behavior:
- When
--sync-delete-missingis set, the tool disables incremental fetching and performs a full scan to avoid deleting unchanged notes. - If
--folderis provided, deletions are limited to notes within that folder subtree. - This flag cannot be used with
--stop-after.
- The first run is a full scan and can take a long time on large note sets.
- After
last_syncis recorded, subsequent runs only fetch notes modified after that timestamp. - If a run is interrupted before completion,
last_syncis not updated. The next run will still perform a full scan.
- If you interrupt a run, any notes already inserted remain in the DB (partial results are saved).
--sync-delete-missingis powerful; use it only if you expect the target DB to mirror Notes exactly for the selected scope.
Create a virtual environment and install dependencies with uv:
cd apple-notes-to-sqlite
uv venv
source .venv/bin/activate
uv pip install -e '.[test]'Run tests:
uv run pytestBy default:
- The tool stores
last_syncin async_statetable. - On subsequent runs it fetches only notes with
modification date > last_sync. - Updates are applied only when the stored
updatedvalue has changed.
If you need to force a full resync, delete the sync_state table or the last_sync row.