Automatically refiling Org Mode headings based on tags

| org, emacs

I have lots of different things in my Org Mode inbox. Following the PARA method, I want to file them under projects, areas, resources, or archive so that I can find related things later. Actually, no, I don't want to refile them. I do want to be able to:

  • find all the pieces related to something when I'm ready to start working on a task
  • find useful links again, especially if I can use my own words

Refiling is annoying on my phone, so I tend to wait until I'm back at my computer. But even with org-refile-use-outline-path set to file and the ability to specify substrings, there's still a bit of friction.

Tagging is a little easier to do on my phone. I can add a few tags when I share a webpage or create a task.

I thought it would be nice to have something that automatically refiles my inbox headings tagged with various tags to other subtrees where I've set a :TAG_TARGET: property or something like that. For example, I can set the TAG_TARGET property to emacsconf to mean that anything tagged with :emacsconf: should get filed under there.

(defcustom my-org-refile-to-ids nil
  "Searches and IDs."
  :group 'sacha
  :type '(repeat (cons string string)))

(defun my-org-update-tag-targets ()
  (setq my-org-refile-to-ids
        (let (list)
           (lambda ()
             (cons (concat "+" (org-entry-get (point) "TAG_TARGET"))
           "TAG_TARGET={.}" 'agenda)))
  (customize-save-variable 'my-org-refile-to-ids my-org-refile-to-ids))

(defun my-org-add-tag-target (tag)
  (interactive "MTag: ")
  (org-entry-put (point) "TAG_TARGET" tag)
  (push (cons (concat "+" tag) (org-id-get-create)) my-org-refile-to-ids)
  (customize-save-variable 'my-org-refile-to-ids my-org-refile-to-ids))

;; Based on
(defun my-org-refile-matches-to-heading (match target-heading-id &optional scope copy)
  "Refile all headings within SCOPE (per `org-map-entries') to TARGET-HEADING-ID."
  (if-let (target-marker (org-id-find target-heading-id t))
      (let* ((target-rfloc (with-current-buffer (marker-buffer target-marker)
                             (goto-char target-marker)
                             (list (org-get-heading)
                                   (buffer-file-name (marker-buffer target-marker))
             (headings-to-copy (org-map-entries (lambda () (point-marker)) match scope)))
         (lambda (heading-marker)
           (with-current-buffer (marker-buffer heading-marker)
             (goto-char heading-marker)
             (org-refile nil nil target-rfloc (when copy "Copy"))))
         (nreverse headings-to-copy))
        (message "%s %d headings!"
                 (if copy "Copied" "Refiled")
                 (length headings-to-copy)))
    (warn "Could not find target heading %S" target-heading-id)))

(defun my-org-refile-to-tag-targets ()
  (dolist (rule my-org-refile-to-ids)
    (my-org-refile-matches-to-heading (car rule) (cdr rule))))

So when I'm ready, I can call my-org-refile-to-tag-targets and have lots of things disappear from my inbox.

Next step might be to write a function that will refile just the current subtree (either going straight to the tag target or prompting me for a destination if there isn't a matching one), so I can look at stuff, decide if it needs to be scheduled first or something like that, and then send it somewhere. There must be something I can pass a property match to and it'll tell me if it matches the current subtree - probably something along the lines of org-make-tags-matcher

Anyway, just wanted to share this!

This is part of my Emacs configuration.
You can comment with Disqus or you can e-mail me at