Generating dynamic file names

Is it possible to use a capture template that can reference a variable once captured, so an author can be used for both the file name and text in the note?

Example:

;; /org/templates/book-note-template.org
* Source:
Author: %^{author}
Title: ${title}

;; config.el

("b" "book" plain
(file "~/org/templates/book-note-template.org")
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}-${author}.org" "#+title: ${title}\n")
:unarrowed t)

The following works on my end. You seem to be using Doom, so I can’t account for Doom’s peculiarity (if any; I don’t even know).

This method prompts for the author for the first time, and then pass it to the next one.

(cl-defmethod org-roam-node-author ((node org-roam-node))
  (let ((author (cdr (assoc-string "AUTHOR" (org-roam-node-properties node)))))
    (if author author
      (setq author (read-string "Author: "))
      (push (cons "AUTHOR" author) (org-roam-node-properties node))
      author)))

Edit: Changed the code to use push instead of setf – the properties of the node (org-roam-node-properties) may not be nil if you are capturing to an existing node. I think we should use push to add to the list rather than set it anew.

I also changed the template org file – note author uses ${prop-name}, different from your original (not sure if the original way works – didn’t test).

* Source:
Author: ${author}
Title: ${title}

Basically, you can use your capture-template as is. Just add the method and change the org file.

1 Like

Thank you for the implementation!

I’ve tried it on my end, modifying to ${auithor} and receive this error:

let*: Symbol’s function definition is void: \(setf\ org-roam-node-properties\)

The error seems to be suggesting that Emacs does not know about org-roam-node-properties. I cannot be certain from this error message alone. I don’t know how to repeoduce this error—the code just works now as it did before.

If my understanding is correct, then the error means that org-roam has not been loaded yet. The reason why I say this is because org-roam-node-properties is part of org-roam.

I suggest you test the code after you explicitly load org-roam (e.g. with M-x load-library).

Does this make sense to you? Do you know how to move forward?

Absolutely, this makes sense. As you mentioned, I am using doom emacs and am loading these in an (after! org) block, which likely is the difference. I have checked and explicitly loaded org-roam, org-roam-properties but still run into the same issue.

While working through this I did rewrite a new configuration which does work for me, so I’ll share it here if others are looking for similar functionality in the future. It does work slightly differently than described. It uses concatenation to dynamically generate titles/file names, the only awkward part is because I’m prompted for a node on capture, I have to arrow up so that none are selected before going to the template.

Here’s the configuration:

(defvar my/org-roam-global-book-title nil)
(defvar my/org-roam-global-author nil)

(cl-defmethod org-roam-node-author ((node org-roam-node))
  (or (cdr (assoc-string "AUTHOR" (org-roam-node-properties node)))
      (read-string "Author: ")))
(defun my/org-roam-book-capture ()
  (let ((book-title (read-string "Book Title: "))
        (author (org-roam-node-author org-roam-capture--node)))
    (setq my/org-roam-global-book-title book-title)
    (setq my/org-roam-global-author author)
    (org-capture-fill-template
     (concat "* Source:\n"
             "Author: " author "\n"
             "Title: " book-title "\n"
             "Reviewed date: %<%Y%m%d>\n\n"
             "* Summary:\n\n"
             "* Notes:\n\n"
             "%?\n"))))
(setq org-roam-capture-templates
      `(("n" "note" plain
         "%?"
         :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
         :unarrowed t)

        ("d" "daily" plain
         "* daily quests: \n\nH: \nW: \nR: \nM: \n\n\n* 5\n** %?"
         :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
         :unarrowed t)

        ("b" "book" plain
         (function my/org-roam-book-capture)
         :if-new (file+head (lambda () (concat "%<%Y%m%d%H%M%S>-"
                                               (replace-regexp-in-string " " "-"
                                                 (format "%s-%s"
                                                   my/org-roam-global-book-title
                                                   my/org-roam-global-author))
                                               ".org"))
                            (lambda () (concat "#+title: "
                                               my/org-roam-global-book-title
                                               " - "
                                               my/org-roam-global-author "\n")))
         :unarrowed t)))

;; templates/book-note-template.org

* Source:
Author: ${author}
Title: ${book-title}
Reviewed date: %<%Y%m%d>

* Summary:

* Notes:

%?

Thank you again for your help on this nobiot!

1 Like

For this awkwardness, you might like to create your own command. This way you can skip the initial selection of a node / title. See below. You can evaluate this expression; it works on my end. I think you have the skill to adapt it.

  (org-roam-capture- :goto nil
                   :info nil
                   :keys nil
                   :templates '(("d" "default" plain "%?" :target
  (file+head "%<%Y%m%d%H%M%S>.org" "#+title: Some String
")
  :unnarrowed t))
                   :node (org-roam-node-create)
                   :props '(:immediate-finish nil))