Setting up Hugo with Denote
Introduction
Geez, what a journey! OK so we are going to start this blog thing, you know. And apparently, the engineering impulse to premature optimisation was strong. So I ended up setting up Denote, a very interesting Emacs package I am currently exploring for note taking, to also manage my posts here. The reason is simple: Denote handles collections of files you hold in specific directories, tracks them with non mutable identifiers (defaulting to the ISO timestamp), and handles you a collection of functions to manage them on the fly. You can quickly see why I found this model could have been useful for managing a blog in Org mode (which by the way is the default file type for Denote).
Why Denote
Denote basically helps you keep your file names consistent. For example, the file name for this post is
20260106T162123--setting-up-hugo-with-denote__blog_denote_emacs_hugo_meta.org
Why? Because it was written starting at 20260106T162123, that means year 2026, month 01 (January), day 06th, at time (T) 16 hours, 21 minutes, 23 seconds. This is the identifier, that if you are a single person managing their files, with a good approximation can act as as a primary key, as you cannot create more than one file at the same time. Then, after the -- comes the title, sluggified, that is lowercased, and with hyphens instead of spaces. After the __ there are the notes, separated by _ (they are in alphabetical order, but that is just a convention that Denote helps you with). There could also be a so called signature, that helps differentiating files that should be connected together (like part-01, part-02 etcetera), but for this file we haven’t one.
Denote then populates the start of the file with the same information, and since this is an Org file, it uses the Org syntax for tags, info and the like:
#+title: Setting up Hugo with Denote
#+date: [2026-01-06 Tue 16:21]
#+filetags: :blog:denote:emacs:hugo:meta:
#+identifier: 20260106T162123
Finally, I could add
#+hugo_base_dir: ../
#+export_file_name: setting-up-hugo-with-denote
These options are used by the first of the two translation layers used to generate the website: ox-hugo (Org-export-hugo), and the proper Hugo program. The first option tells ox-hugo where the root of the website directory is. Since I keep the Org post files in ~/ensigns-blog/content-org/ directory, I need to tell the export engine that ~/ensigns-blog/ is the website directory. by default, it will translate the Org file in a Markdown (.md) file, in the <website_root>/content/ directory. Why? Because that’s where Hugo expects to find the Markdown files. The first layer is necessary, because Hugo cannot directly read Org files. The second option (export_file_name) is general to all Org export engines, and it specifies which name the output file will have. Now, this is not only for good storage hygiene. Hugo will take each .md file in the content/ directory, and it will turn it in a corresponding .html file, that will be then served by the server. That means (you guessed), it will end up being the permalink of the post.
Now we have the best of both worlds: Org mode can write all the post with a very nice syntax; Denote help linking and managing the files (as well as keeping it in chronological order); ox-hugo turns every post in Markdown; and finally Hugo turns every Markdown file in a web page.
Note that none of the Denote functions is technically needed to keep the posts tidy: Org already provides a linking syntax to connect files, the front matter can be written (of course) by hand, as well as the file name. It just helps.
Easing using Denote
Now the problem becomes: what if we already have a Denote managed directory on our system? We don’t want to accidentally use personal files in our website, do we? They won’t be correctly exported in the final website. Denote can manage multiple directories, but by default it considers all of them to be part of the same logical “file database”. To have a separate directory, it introduces a concept called silos: just as in the IT vocabulary, a silo is a defined, separated data collection, in Denote silos are collections of files you don’t want to mix. Perfect for us! But wait, how do we set them up? Well, the simplest approach can be to set the variable denote-directory, that indicates the root directory that hosts your files. But where? We have three options:
- Globally, in your init file
- Directory base, with a
.dir-locals.elin the website directory - On a per function, or per keystroke, basis
Option 1 is clearly to rule out, as we couldn’t then use our normal notes. Option 2 is somehow partial as well: depending on the directory we are in, we would switch between silos, but helps when using the already written posts (as we play to link them only from other posts), but when we want to open or write a new one. We would first need to navigate (perhaps using Dired) to the website directory, and then we could use Denote. Which, as you may understand, greatly reduces the effectiveness of Denote in the first place. This means that we would be better off by tweaking the Denote function.