GitMyNotes adds convenient version control, off-site backups, and optional collaboration to your macOS Notes.
A lightweight macOS utility using Python and AppleScript to sync your macOS Notes to your public or private GitHub repos (or, optionally, a local only backup). The GitMyNotes solution exports Notes to Markdown in a local directory, while preserving folder organization and formatting. It features out-of-the-box common sense defaults or user-driven customization, a local audit trail file with checkpoint features for interrupted backups, and secure GitHub integration. All processing happens on your machine, with the only external data transmission secured by your enterprise-grade GitHub authentication credentials.
Do you use Notes? Do you use GitHub? Then undoubtedly yes!
Do you use Notes and could start with GitHub? Then also yes!
writing is re-writing
Transform your macOS Notes into a version-controlled archive on GitHub. Use GitMyNotes to preserve and maintain your most excellent lines via versioning to track the full evolution of your work. Learn how to GitHub using branches and forks to explore your ideas, in trusted collabs, or on your own.
IT team gotta keep copies
Concerned about a fleet of MacBooks, and all the useful, non-shareable, and non-collaborative notes just one cup of coffee away from disaster? Use GitMyNotes on a cronjob on every Mac to keep versioned copies of valuable Notes.
prepared tech workers win
No one can predict the future. Losing a job or a role shouldn't mean losing your personal MacOS Notes as well. Set up a free GitHub repo and use GitMyNotes to back up the folders that hold your personal reminders, thoughts, and plans.
but... I dont use Github (yet?)
Simply set '--local-only' when you run the command to create, surprise, local-only backups. No GitHub required. But, really, you should set up a free GitHub repo.
- MacOS with Notes app and AppleScript (ships with every Mac)
- Python 3.x+
- Install dependencies:
pip install -r requirements.txt(justruamel.yaml, used for round-trip-safe config edits) - GitHub repo (eg,
https://github.com/<MYUSERNAME>/gitmynotes) accessible from the Mac running this script. Can be public or private, must be configured working auth credentials, etc.
-
First, clone the repo:
git clone https://github.com/mariochampion/gitmynotes.git -
Next,
cdinto gitmynotes dir:cd gitmynotes -
Then, in file
gmn_config.yaml, set the required Github url to your Github url-- REQUIRED TO CHANGE--
set
DEFAULT_GITHUB_URLto the the repo where you want to store Notes.example:
'DEFAULT_GITHUB_URL': 'https://github.com/<ChangeMe>/gitmynotes'OPTIONAL: Leave as-is or change the additional
DEFAULT_*configs the the file.
That's it! Now run the script:
python gitmynotes.py to run against default "Notes" folder.
Or be more specific with:
python gitmynotes.py [--folder='<notesFolderName>' --max-notes=<N> --print-level=results]
Learn more with:
python gitmynotes.py --help
Do this once: set an alias in your bash profile:alias gitmynotes='python gitmynotes.py'
From then on, just use gitmynotes as in gitmynotes --folder='myPythonNotes' --maxnotes=20
or gitmynotes --help
--auto walks every folder GitMyNotes already knows about and backs up only what's changed since each folder's last successful run. It pairs with --yes to make GitMyNotes safe to run unattended from cron, a Cowork routine, or any other scheduler.
The workflow is two steps:
- Seed each folder once. Run
gitmynotes --folder=<name> --max-notes=1for each folder you want auto mode to manage. The first successful run records a per-folder modification-date watermark ingmn_config.yaml'sUSAGE_FOLDERS_PROCESSED. - From then on, use
--auto. Each subsequent run checks each known folder, skips folders with no changes since their watermark, processes the rest, and advances each folder's watermark on success. Folders with a still-null watermark are skipped with a yellow warning telling you which--folder=<name>to run to seed them.
Per-folder failures are isolated — one folder hitting an error (e.g. a folder you renamed or deleted in Notes.app) doesn't stop the others. The aggregate exit code reflects the worst per-folder outcome: 0 if every folder was clean (or had nothing to do), 2 if any folder ended partial or hit a hard failure.
For an unattended cron / Claude Co-work-routine run, the typical command is:
gitmynotes --auto --yes
Note: The reddit_.py are WorkInProgress. Full guidance forthcoming.
-- Local --
- New Notes folder:
<notesFolderName>_GitMyNotesto store processed notes - New GitMyNotes audit file:
<notesFolderName>.csvto track processed notes. The "Last Modified" column is recorded asYYYY-MM-DD HH:MM:SS(a locale-independent ISO-shaped timestamp) so it sorts and parses cleanly. (Audit files generated by versions of GitMyNotes prior to mid-2026 used a locale-formatted date string in this column; if you upgrade an existing repo, your CSV will hold a mix of formats — older rows in the original style, newer rows in ISO. Each row is internally consistent.) - Markdown copy of Notes from folder:
DEFAULT_EXPORT_PATH/<my-exported-note.md> - POTENTIALLY: Unsupported notes (often a note with JUST an image) will be moved to
<notesFolderName>_unsupported
-- Remote --
- New GitHub directory:
DEFAULT_GITHUB_URL/DEFAULT_NOTES_WRAPPERDIR - New Github sub-dir mapped to Notes folders:
DEFAULT_GITHUB_URL/DEFAULT_NOTES_WRAPPERDIR/<notesFolderName> - Github copy of Notes from folder:
DEFAULT_GITHUB_URL/DEFAULT_NOTES_WRAPPERDIR/<notesFolderName>/<a-note.md> - OPTIONAL - GitMyNotes audit file:
<notesFolderName>.csv
macOS Notes distinguishes two states for password-protected notes: closed (currently showing the lock screen, body hidden) and open (unlocked in your current Notes.app session, body visible). GitMyNotes handles these differently:
- Locked and closed — AppleScript returns an empty body. GitMyNotes commits a stub file (title + dates + a marker saying the content wasn't exported) to GitHub. No cleartext leaves your Mac.
- Locked and open — AppleScript returns the real content, same as any unlocked note. GitMyNotes has no way to tell this case apart, so the cleartext body will be committed to GitHub.
macOS does not expose a "locked" status to AppleScript, so GitMyNotes cannot detect this automatically. If you want to keep a locked note's contents private, close it (click away so it re-locks, or close the Notes app) before running GitMyNotes. A note currently showing its lock screen is safe; a note you've unlocked in this session is not.
Export macOS Notes to GitHub repo (or local-only directory)
usage: gitmynotes.py [--folder FOLDER] [--maxnotes MAX_NOTES] [--restore RESTORE_NOTES]
[--force] [--batch-size BATCH_SIZE] [--export-path EXPORT_PATH]
[--github-url GITHUB_URL] [--newline-delimiter NEWLINE_DELIMITER]
[--audit-file-ending AUDIT_FILE_ENDING] or [-h]
options:
FREQUENTLY USED
-h, --help show this help message and exit
--folder FOLDER [str] Specific Notes folder to export.
(default: 'Notes')
--max-notes, --maxnotes, --max MAX_NOTES
[int] Maximum number of notes to process in this run.
--restore-notes RESTORE_NOTES, --restorenotes RESTORE_NOTES, --restore RESTORE_NOTES
[str] Options: 'empty' or 'always' or 'never'.
Determines when to move notes from '<FOLDER>___GitMyNotes' back
to their original source folder.
Set 'empty' to restore when notecount is 0 in source folder,
Set 'always' to restore at the end of each GitMyNotes run.
Set to 'never' to, well, never move notes back to source folder.
(default: 'empty')
--print PRINT, --print-level PRINT
[str] Optional set to 'none', 'results', 'debug', 'all' for increasing
details. Useful for tracking code flow and general debugging.
(default: 'all')
--auto [bool] Use as '--auto' (no value allowed) to back up every folder
GitMyNotes already knows about (those listed in 'USAGE_FOLDERS_PROCESSED'
in 'gmn_config.yaml'), but only the notes that have changed since each
folder's last successful run. Skips folders with no recorded watermark
(instructs you to seed them with one manual '--folder=NAME' run first).
Suppresses the 5x-batch confirmation entirely. Per-folder failures are
isolated -- one stale folder doesn't stop the others. Combine with
'--yes' for unattended scheduled / cron / Cowork-routine runs.
'--folder' is ignored under '--auto'.
(default: off)
LESS FREQUENTLY USED
--batch-size BATCH_SIZE
[int] The number of notes to convert, and git add/commit/push per loop,
calculated a max-notes/batch-size. Especially useful for initial runs.
(default: 10)
--export-path EXPORT_PATH, --exportpath EXPORT_PATH
[str] Path to export the notes (default: ~/Documents/gitmynotes)
--force [bool] Use as '--force' (no 'true' or 'false' value allowed)
Use to over-ride to the default required user confirmation to process
the full count of Notes in the specified folder when it exceed
5x the batch size -- which could be hundreds of notes and
could take a looooong time.(default: confirmation will be required)
--yes, --non-interactive
[bool] Use as '--yes' or '--non-interactive' (no value allowed)
for scheduled / non-terminal runs (cron, Cowork routines, CI).
Implies '--force' (skips the 5x-batch confirmation) and also
fails fast with a clear error if 'DEFAULT_GITHUB_URL' still
contains the '<ChangeMe>' placeholder, instead of hanging on
the interactive setup prompt.
(default: interactive prompting is allowed)
--local-only [bool] Use as '--local-only' (no 'true' or 'false' value allowed)
Use to over-ride to the default action of backing up notes to GitHub.
When set, only a local copy of notes will be made.
(DEFAULT: Send notes to GitHub repo)
--github-url GITHUB_URL, --githuburl GITHUB_URL
[str] GitHub repository URL.
(default: https://github.com/<ChangeMe>/gitmynotes)
--notes-account NOTES_ACCOUNT, --notesaccount NOTES_ACCOUNT
[str] macOS Notes account name (e.g. 'iCloud',
'On My Mac'). Used to scope every AppleScript op to a
single account. Most users want 'iCloud'.
(default from config: 'iCloud')
--newline-delimiter NEWLINE_DELIMITER, --newlinedelimiter NEWLINE_DELIMITER
[str] Default CSV newline delimiter (default: '|||')
--audit-file-ending AUDIT_FILE_ENDING, --auditfileending AUDIT_FILE_ENDING
[str] The audit file extension (default: '.csv')
GitMyNotes exits with one of three codes so you can chain it cleanly in shell scripts and scheduled / non-interactive runs (cron, Cowork routines, CI). Check with echo $? after a run.
-
0— full success. The run did everything it set out to do. Includes happy-path runs, no-op runs (empty folder,--max-notes 0), runs whose only "issue" was closed-locked notes committed as stubs (that's the documented behavior, not a partial outcome),--local-onlyruns that completed the local export, and runs where you typedxat the 5x-batch confirmation prompt (clean user-initiated abort -- nothing happened). -
1— hard failure. The run could not proceed. Triggers:DEFAULT_GITHUB_URLstill contains<ChangeMe>and--yesis set;--folderwas omitted andDEFAULT_NOTES_FOLDERis empty in the config; or other config / startup errors that make any further work pointless. No useful work was done. -
2— partial success. The run did some work but didn't complete cleanly. Triggers: an unsupported (image-bearing) note aborted a batch mid-flight (notes before it were exported and committed; the bad note went to<folder>_unsupported; remaining notes are left for the next run); the git push failed after a successful local commit (work is committed locally and recoverable on the next run); or moving the processed notes back into<folder>__GitMyNotesfailed after a successful export and commit.
A partial-success run is safe to re-run -- the same notes won't be duplicated, and the work that already landed stays landed.