The Templating System

Rather than creating blank files on org-roam-insert and org-roam-find-file, it is may be desirable to prefill the file with content. This may include:

  • Time of creation
  • File it was created from
  • Clipboard content
  • Any other data you may want to input manually

This requires a complex template insertion system, but fortunately, Org ships with a powerful one: org-capture. However, org-capture was not designed for such use. Org-roam abuses org-capture to some extent, extending its syntax. To first understand how org-roam's templating system works, it may be useful to look into org-capture.

Org-roam's templates can be customized by modifying the variable org-roam-capture-templates.

Org-roam Templates

The org-roam capture template extends org-capture's template with 2 additional properties:

  1. :file-name: This is the file name template used when a new note is created. Notes can be placed in sub-directories by prepending them to the filename (sub/file-name)
  2. :head: This is the template that is inserted on initial note creation.

Org-roam Template Expansion

Org-roam's template definitions also extend org-capture's template syntax, to allow prefilling of strings. In many scenarios, org-roam-capture--capture is passed a mapping between variables and strings. For example, during org-roam-insert, a title is prompted for. If the title doesn't already exist, we would like to create a new file, without prompting for the title again.

Variables passed are expanded with the ${var} syntax. For example, during org-roam-insert, ${title} is prefilled for expansion. Any variables that do not contain strings, are prompted for values using completing-read.

After doing this expansion, the org-capture's template expansion system is used to fill up the rest of the template. You may read up more on this on org-capture's documentation page.

For example, take the template: "%<%Y%m%d%H%M%S>-${title}", with the title "Foo". The template is first expanded into %<%Y%m%d%H%M%S>-Foo. Then org-capture expands %<%Y%m%d%H%M%S> with timestamp: e.g. 20200213032037-Foo.

All of the flexibility afforded by emacs and org-mode are available. For example, if you want to encode a UTC timestamp in the filename, you can take advantage of org-mode's %(EXP) template expansion to call format-time-string directly to provide its third argument to specify UTC.

("d" "default" plain (function org-roam--capture-get-point)
     "%?"
     :file-name "%(format-time-string \"%Y-%m-%d--%H-%M-%SZ--${slug}\" (current-time) t)"
     :head "#+TITLE: ${title}\n"
     :unnarrowed t)

Similarly, if you want to change how titles are transformed into slugs, you can override org-roam--title-to-slug. For example, to use hyphens instead of underscores:

  (defun org-roam--title-to-slug (title)
    "Convert TITLE to a filename-suitable slug. Uses hyphens rather than underscores."
    (cl-flet* ((nonspacing-mark-p (char)
                                  (eq 'Mn (get-char-code-property char 'general-category)))
               (strip-nonspacing-marks (s)
                                       (apply #'string (seq-remove #'nonspacing-mark-p
                                                                   (ucs-normalize-NFD-string s))))
               (cl-replace (title pair)
                           (replace-regexp-in-string (car pair) (cdr pair) title)))
      (let* ((pairs `(("[^[:alnum:][:digit:]]" . "-")  ;; convert anything not alphanumeric
                      ("--*" . "-")  ;; remove sequential underscores
                      ("^-" . "")  ;; remove starting underscore
                      ("-$" . "")))  ;; remove ending underscore
             (slug (-reduce-from #'cl-replace (strip-nonspacing-marks title) pairs)))
        (s-downcase slug))))

This templating system is used throughout org-roam templates.

Template examples

Here I walkthrough the default template, reproduced below.

("d" "default" plain (function org-roam--capture-get-point)
     "%?"
     :file-name "%<%Y%m%d%H%M%S>-${slug}"
     :head "#+TITLE: ${title}\n"
     :unnarrowed t)
  1. The template has short key "d". If you have only one template, org-roam automatically chooses this template for you.
  2. The template is given a description of "default".
  3. plain text is inserted. Other options include Org headings via entry.
  4. (function org-roam--capture-get-point) should not be changed.
  5. "%?" is the template inserted on each call to org-roam-capture--capture. This template means don't insert any content, but place the cursor here.
  6. :file-name is the file-name template for a new note, if it doesn't yet exist. This creates a file at path that looks like /path/to/org-roam-directory/20200213032037-foo.org.
  7. :head contains the initial template to be inserted (once only), at the beginning of the file. Here, the title global attribute is inserted.
  8. :unnarrowed t tells org-capture to show the contents for the whole file, rather than narrowing to just the entry.

Other options you may want to learn about include :immediate-finish.