Exploring the Denote Emacs Package

Some Analsysis of Features

As of I use Org-Roam 📖 for writing and linking. Those notes can be glossary entries, blog posts, people, epigraphs, or just about anything text.

Org-Roam utilizes a SQLite database for storing and linking metadata. This is a feature that I consider a nice to have.

An emerging note taking utility is Denote; in this document I want to explore my usage of Org-Roam; what I’ve come to expect (e.g. consider a “must have”) and what I’ve come to appreciate (e.g. consider a “nice to have”). I then want to look at how to achieve that in Denote.

The foundational tooling that I want:

Org-Mode aware
Note taking that “understands” Org-Mode 📖; or more appropriately that Org-Mode tooling can understand.
Tag each note.
Quick filing
Insert a new note with minimal thought of where it goes.
Link to other notes.
Add metadata that can power my epigraphs and glossary.
Prompt for notes by searching title and tags and other metadata.
Export Links
When I link to a node and export, I want to export the world facing URL.

The above definitions are my “feature list”; I’ll reference those later.

A nice to have feature would be prompting for a file by more advanced searching. An example of that would be as follows:

  • Filter on the programming tag
  • Search the resulting filter set for the words Hello World

Another nice to have feature is collision detection; I don’t want two notes to have the same glossary key, nor alias. Right now I believe Org-Roam enforces unique aliases but not glossary keys.

Diving into Denote

You might be wondering why this document? There’s a two-fold reason. First, I want a place to think about what this change would mean. Second, I want to remember and be able to later evaluate all of the code I’m writing and loading into Emacs 📖 without adding it to my Emacs Configuration.

In away, this post is my sandbox exploration of the Denote package.

With this quick establishment of my note-taking requirements I’m going to explore Denote.

Why explore Denote?

First, I found myself reading the Denote documentation for pleasure. It is one well-documented package; accessible and helpful at learning not just Denote but also Emacs Lisp as well.

More importantly (maybe?) is a statement I recall from a seminar with Neil Jeffries regarding the Oxford Common File Layout (OCFL 📖). The statement was along the lines of favoring Posix, the Unix file system. It is a consistent and underpinning technology that is very likely to continue as other technologies light the stage and fade away. In other words, it has attributes that are ideal for “preservation” of digital objects. Too Long; Didn't Read 📖 Favor it over any other system of preservation.

I’ve long worked in the fields adjacent to digital preservation concerns and have felt the anguish of Fedora Commons wending a path to, from, and back to the sensible consideration of storing things on a file system.

In other words, the sensibility of Denote echoes my wariness of too many tools. However, there’s quite a bit of tooling in Org-Roam that uses SQLite to help facilitate; but the fundamentals of portability remain.

The key consideration is that Org-Roam has one more external dependency than Denote.

Reading Denote’s documentation on Portability, I see common consideration:

Notes are plain text and should remain portable. The way Denote writes file names, the front matter it includes in the note’s header, and the links it establishes must all be adequately usable with standard Unix tools. No need for a database or some specialised software.

Protesilaos Stavrou (aka Prot), the maintainer and originator of Denote, stewards many Emacs packages. A consistent attribute of those packages is fantastic documentation; both inline and of the “README” variety.

Denote’s documentation exemplifies quality documentation.

The Code I Load

When I first started, I had the minimal package declaration (use-package denote :straight t). As I explored I amended that basic declaration to the following code block.

(use-package denote
  :straight t
  :commands (denote-directory)
  :bind ("H-c a" . jf/denote-create-abbreviation)
  :custom ((denote-directory "~/git/org/denote")
	   ;; These are the minimum viable prompts for notes
	   (denote-prompts '(title keywords))
	   ;; I love org-mode format; reading ahead I'm setting this
	   (denote-file-type 'org)
	   ;; And `org-read-date' is an amazing bit of tech
	   (denote-date-prompt-denote-date-prompt-use-org-read-date t)))

(cl-defun jf/denote-create-abbreviation
     (title (denote--title-prompt))
     (abbr (read-from-minibuffer "Abbreviation: ")))
  "Create a `denote' entry for the given TITLE and ABBR.

NOTE: At present there is no consideration for uniqueness."
  (let ((template (concat "#+GLOSSARY_KEY: glossary---" abbr "\n"
			  "#+ABBR: " abbr "\n")))
    (denote title
	    '("glossary" "abbreviation")
	    (f-join (denote-directory) "glossary")

Exploration Notes

I arrived at the above code-block via the following exploration.

Customizing denote-directory

Prior to configuration my denote directory was ~/git/org/main/notes; I’m unclear why it chose this but it was a good guess as my org-roam-directory is ~/git/org and I have a capture template that writes to ~/git/org/main/.

With the configuration I’m partially sequestering my playground and will begin exploring.

Creating jf/denote-create-abbreviation

I had originally started exploring the denote-templates but chose to pursue a more explicit pathway which, based on my knowledge of Lisp, was straight forward and very quick.

There were a few turns that I took. An existing implementation for much of my tooling is that I’m assuming an Org-Mode properties drawer for the GLOSSARY_KEY and ABBR properties.

In Org-Roam the properties for the node go above the TITLE property; however by convention that is not how Denote is structured to work. I made some revisions.

I tested the above by creating a new Denote node for “Digital Humanities” with the abbreviation of “DH”.

Pause To Review Requirements and Reflect

At this point, I have verified checked off 4 of the 7 requirements. And reading ahead of the documentation I see that there are considerations for Linking and Exporting Links as well as searching.

  • Org-Mode aware
  • Tags
  • Quick filing
  • Linking
  • Metadata
  • Search
  • Export Links

At this point, what I really like is the interface to creating a note. The denote function does the magic and allows for me to pass parameters that override the default methods.

Contrast with Org-Roam, where I provide the title/text and then say what the template shouldl be.

I’m also liking the ease at which I could create a function for creating glossary entries. To do that in Org-Roam via capture is certainly doable.

The thing I need to check is how moving from the properties drawer approach I’ve used in Org-Roam varies from using keywords. Most of my interactions with properties are via Org-Mode’s API.

This sounds like the next pathway to explore.

Investigating Moving from Property Drawer to Keywords

Before I get too much further, I need to verify that I can continue to get properties from my notes.

The following function verifies that I can retrieve a property for the Denote note I made.

(cl-defun jf/denote-org-property-from-id (&key id property)
  "Given an ID and PROPERTY return it's value or nil.

Return nil when:

- is not a denote file
- ID is not an `org-mode' file
- property does not exist on the file"
  (when-let ((filename (denote-get-path-by-id id)))
    (when (string= (file-name-extension filename) "org")
      (with-current-buffer (find-file-noselect filename)
	(cadar (org-collect-keywords (list property)))))))

(message "%s" (jf/denote-org-property-from-id :id "20220930T215235"
					       :property "ABBR"))

Let’s Look at Linking

For this, I’ll need another node. I now have two nodes: 20220930T221757 and 20220930T215235. Using denote-link I create a link in 20220930T221757 to 20220930T215235.

Then in 20220930T221757 I call denote-link-backlinks. The backlink buffer is an enumeration of links. Whereas in Org-Roam the backlink and reference buffer includes the surrounding context; a nice feature but not something I consider mandatory.

And below is my must haves:

  • Org-Mode aware
  • Tags
  • Quick filing
  • Linking
  • Metadata
  • Search
  • Export Links

I need to further explain what I mean by this. I am accustomed to using my own jf/org-roam-find-node which wraps org-roam-find-node. I can filter by tags and title. With denote, I have to consider directory structure.

Let’s see about leveraging the Consult package.

(bind-key "H-f" 'jf/denote-find-file)
(defun jf/denote-find-file ()
  "Find file in `denote-directory'"
  (require 'consult-projectile)
  (require 'denote)
  (consult-projectile--file (denote-directory)))

This provides me with the comparable functionality, but requires some reimaginging. However, courtesy of Section 5. The file-naming scheme of Denote documentation I can use the naming convention for tag and filename search.

Prefix the search with - for a tag and and _ for a word. This matches the functionality of what I have.


Reading and testing Denote, I have established feature parity in my functional needs.

What does that mean?

I am prepared to further pursue what it might mean to migrate my some 2800 Org-Roam notes to denote. I just completed a migration of my Hugo 📖 ./data/glossary.yml file to Org-Roam, so I know that it’s not an arduous process to migrate. Read about this data migration in On Storing Glossary Terms in Org Roam Nodes. I already identified the need to move from property drawers to properties that are positioned in the file after the Denote front-matter. I’d need to revisit the Ox-Hugo export process I’ve developed. As well as how I’m exporting and creating links.

I would also want to look at different directories. I like separating the different concerns (e.g. glossary, epigraph) and the ease at which I could set this up.

There is quite a bit more to consider regarding this migration. But it is an interesting (to me) exercise of consideration.