Over the years, I’ve built up quite a collection of notes as Org mode text files. So far, it has proven to be the most expressive and the most robust note-taking modality out of a long list of candidates that I’ve tried.

Note-taking using Org mode has one big drawback however: Mobile accessibility.

In other words, consulting one’s org mode notes database from a mobile device is painful. This should not be the case; notes should be always and instantly available, even on mobile.

In this blog post, I show you how to import your complete org mode notes database into Apple Notes, including typesetting and attachments, using the org mode publishing functionality.

To be clear: Org mode on the desktop remains my primary note-taking system. The goal of importing all of my notes into Apple Notes is only to have my personal knowledge base accessible from my mobile devices.

End result

After configuring and running org-publish, and then importing the whole directory of exported HTML files and attachments into Apple Notes on macOS, your notes will look like one of the two examples below: First macOS, then iOS on the phone.

Note the Emacs-supplied syntax highlighting, and the inline image.

If you import these to your icloud account (the default) the notes will be available on all of your iOS mobile devices.

These imported notes are fully indexed, and hence searchable from all of your devices.

An example note in Apple Notes on macOS.
The same note as above, but now in Apple Notes on iOS.

org-publish configuration

In order to make this happen, we make use of the org-publish functionality. We also configure one or two Apple Notes-specific changes to improve rendering.

Add the configuration below to your init.el.

There are two publish targets: One for the org files (called pkb4000 below), and one for all of the attachments (called pkb4000-static below).

As an aside, pkb4000 is short for Personal Knowledge Base 4000. I chose the name as a joke, as this synced directory of org mode files felt like just the Nth in a long series of knowledge base iterations. Little did I know how well this one would stick.

Remember to change both the :base-directory properties to the top-level directory of your notes database. :publishing-directory is anywhere convenient where you would like to store the published HTML files and attachments.

;; https://orgmode.org/manual/HTML-preamble-and-postamble.html
;; disable author + date + validate link at end of HTML exports
(setq org-html-postamble nil)

(defun org-html-publish-to-html-for-apple-notes (plist filename pub-dir)
  "Convert blank lines to <br /> and remove <h1> titles."
  ;; temporarily configure export to convert math to images because
  ;; apple notes obviously can't use mathjax (the default)
  (let* ((org-html-with-latex 'imagemagick)
          (org-publish-org-to 'html filename
                              (concat "." (or (plist-get plist :html-extension)
                              plist pub-dir)))
    ;; 1. apple notes handles <p> paras badly, so we have to replace all blank
    ;;    lines (which the orgmode export accurately leaves for us) with
    ;;    <br /> tags to get apple notes to actually render blank lines between
    ;;    paragraphs
    ;; 2. remove large h1 with title, as apple notes already adds <title> as
    ;; the note title
     (format "sed -i \"\" -e 's/^$/<br \\/>/' -e 's/<h1 class=\"title\">.*<\\/h1>$//' %s"

(setq org-publish-project-alist
         :base-directory "~/Dropbox/notes/pkb4000/"
         :publishing-directory "~/Downloads/pkb4000"
         :recursive t
         :publishing-function org-html-publish-to-html-for-apple-notes
         :section-numbers nil
         :with-toc nil)
         :base-directory "~/Dropbox/notes/pkb4000/"
         :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
         :publishing-directory "~/Downloads/pkb4000/"
         :recursive t
         :publishing-function org-publish-attachment

Publish your database and import to Apple Notes

Start the process by M-x org-publish, at which point Emacs will ask you to select a target. You have to org-publish both of the targets.

If you have a large database, Emacs might report errors in your org files. Fix these, and restart the process. org-publish caches its output, so re-runs should not take that long.

After a successful publish, import the whole :publishing-directory into Apple Notes on macOS by selecting File | Import from the menu.

Apple Notes will create a new Imported Notes folder containing your whole notes hierarchy.

This process can be easily repeated when you want to refresh the database on your Apple Notes, but unfortunately Notes will create a new Imported Notes N folder.

Alternatively, you can re-publish, and then import just changed HTML files one by one, after which you’ll have to move them back into the correct Apple Notes folder.


This solves the problem of being able to search rapidly and consult your whole org mode notes database using your iOS mobile device.

However, it does not yet solve the problem of importing Apple Notes you create on the mobile device back into Orgmode. This is something one could consider trying to solve using AppleScript.

Whatever the case may be, this is still a nice improvement over my previous workflow!

Bonus round: Convert orgmode buffer to Apple Note using AppleScript

Before I tried org-publish, I worked on some emacs-lisp and AppleScript to convert the current org mode buffer to HTML, and then to inject that into Apple Notes using Apple Script.

I am posting this here in case it might be useful to someone. However it is NOT required for the org-publish workflow described above.

This assumes that you have an orgmode folder.

Although far more humble than org-publish, this code will only create a new note if it does not exist yet. If the note already exists, it will simply update its contents to the current org file.

;; https://www.emacswiki.org/emacs/string-utils.el
(defun string-utils-escape-double-quotes (str-val)
  "Return STR-VAL with every double-quote escaped with backslash."
    (replace-regexp-in-string "\"" "\\\\\"" str-val)))

(defun string-utils-escape-backslash (str-val)
  "Return STR-VAL with every backslash escaped with an additional backslash."
    (replace-regexp-in-string "\\\\" "\\\\\\\\" str-val)))

(setq as-tmpl "set TITLE to \"%s\"
set NBODY to \"%s\"
tell application \"Notes\"
        tell folder \"orgmode\"
                if not (note named TITLE exists) then
                        make new note with properties {name:TITLE}
                end if
                set body of note TITLE to NBODY
        end tell
end tell")

(defun oan-export ()
  (let ((title (file-name-base (buffer-file-name))))
    (with-current-buffer (org-export-to-buffer 'html "*orgmode-to-apple-notes*")
      (let ((body (string-utils-escape-double-quotes
                   (string-utils-escape-backslash (buffer-string)))))
        ;; install title + body into template above and send to notes
        (do-applescript (format as-tmpl title body))
        ;; get rid of temp orgmode-to-apple-notes buffer