Templating Denote-style naming in Obsidian

· 1226 words · 6 minute read

I really do like the Denote file naming convention as a sort of future-proofed forever format, and I’ve been fussing at this idea that Obsidian could make a good mobile Denote file browser. There are some things around how internal links work I haven’t dug into yet, but it’s something to play with.

This morning over tea I applied myself to learning how Templater works. It’s an Obsidian plugin that lets you run JavaScript on notes, with access to stuff like the data in the YAML frontmatter and the ability to build your own functions and make them globally reusable. With Templater, you can do the automation needed to produce Denote-style filenames with a very Denote-style “name the directory, name the file, name the tags, start editing” workflow. It sounds sort of Rube-Goldbergy, but if JavaScript is to Obsidian as elisp is to Emacs, it’s just “extensible tools require code.”

So this is a Templater template to make a new note with YAML frontmatter and user-input tags. You can see that it’s just template + a few strftime variations + a prompt:

title: <% tp.file.title %> // title comes from user input using the QuickAdd plugin
date: <% tp.file.creation_date("YYYY-MM-DDTHH:mm:ssZ") %>
tags: <%* let tags = await tp.system.prompt("Enter tags, space delimited", "")%><% tags %>
identifier: "<% tp.file.creation_date("YYYYMMDDTHHmmss") %>"

(There’s a small bootstrapping problem of how a new note can have a title to drop into the frontmatter — that’s solved with a plugin that prompts for the title before creating the note.)

So … invoke that template and you get a note that has the characteristics of a Denote note with markdown-yaml content. Its actual filename, however, will be following Obsidian conventions: Whatever the name is.md on the filesystem.

That’s not okay, because Denote’s also a file-naming convention, and one I prefer because it includes tags in the filename along with date and title. If you just keep your filenames proper, most of what you care about for long-term portability is just sitting right there in the filename, easily extractable for export/migration/etc.

Templater is able to run scripts that suck in the data in the frontmatter, and it’s able to manipulate filenames and locations. So, this is a user function that takes title, ident, and tags params and spits out a Denote-compatible filename:

function slugify(title,ident,tags) {
  title = title.replace(/^\s+|\s+$/g, ''); // trim leading/trailing white space
  title = title.toLowerCase(); // convert string to lowercase
  title = title.replace(/[^a-z0-9 -]/g, '') // remove any non-alphanumeric characters
           .replace(/\s+/g, '-') // replace spaces with hyphens
           .replace(/-+/g, '-'); // remove consecutive hyphens
  tags = tags.replace(/ /g,"_");

  return ident + "--" + title + "__" + tags;
module.exports = slugify

Once you know what the filename should be, you have to actually change it on the filesystem. This is a second Templater “template” of JavaScript you can run on a note once it has been created:

	identifier =  tp.frontmatter["identifier"];
	title = tp.frontmatter["title"];
	tags = tp.frontmatter["tags"];
	slugged =  tp.user.slugify(title, identifier, tags);
	tp.file.rename(slugged); -%>

(Looking at the slugify function and that Templater code, I think you can just pass the tp object into the function to move some busy-ness into the function and away from your file template. I dunno. I’m just trying to get ping right now.)

So at that point, you have a Markdown note sitting in your Obsidian vault with a filename that conforms to Denote’s naming convention. If you visit your vault directory in Emacs using dired, the files are all fontified for Denote-browsing goodness.

And that’s where I ran out of tea and needed to go do some paying work.

I don’t yet know how to get that last function to run at the point of note creation. There’s surely some way to get Templater code to hold off on firing the next step in a script until you can get all the data in place, but I don’t know what it is. So I have the renaming template bound to a hotkey and just tap Ctrl-Cmd-r to trigger the rename once the note is ready. It’s good to have it exposed as its own command, because part of the Denote workflow involves keeping good file naming hygiene when you change note metadata, I’d just like to automate that part at time of creation, too.

The last ingredient I’m using for all this is the Obsidian front-matter title plugin. It just consults the YAML frontmatter for the title: property and uses that to display note names instead of the filesystem name. That makes directories of files a little more legible, makes tab names less noisy, etc. It does for you in Obsidian some of what Denote Menu and similar do in Emacs, pulling Denote’s useful but visually cluttered naming convention more firmly in the “human-readable” direction. It’s optional to this exercise, but preferred, especially when on mobile, where there’s less real estate to burn on long filenames.

If you visit the file in Denote, you can mess with the frontmatter and use Denote’s denote-rename-file-using-front-matter command to update the file name. If you mess around with it in Obsidian, you can run that Templater template to do the same.

If you run obsidian.el on top of all of this, it becomes possible to navigate your notes in Emacs, insert links from Emacs, etc. etc. You can’t just tap a link from the keyboard — you have to use obsidian.el’s navigation commands — but that’s not terrible.

So to solve the mobile thing, you’ve got this approach — customizing Obsidian with a few plugins and some light code to make your vault look like a Denote corpus — and you’ve got the thing I did to provide a searchable web interface.

The Obsidian route gives you a more complete mobile experience. You can make notes on the go, you have more flexibility for searching your notes, etc.

The web approach is more compact: Do some Emacs configuration, use some commodity infra with Tailscale and a Synology, then just use Denote in its native form, which is org- and desktop-centric.

Given my usage patterns, either seems fine?

Switching to Markdown for Denote satisfies a part of my brain that doesn’t like trying to script org-mode migrations and that also understands “org-mode vs. Markdown” is another one of those “Beta vs. VHS” situations I need to just accept. Commensurately, it irritates the part of my brain that completely got it when my tech writing team told me they wanted to migrate from Markdown to DITA, maybe a little before we were really there on the maturity curve.

Anyhow, it’s a hobby.

espanso 🔗

I don’t know what on Earth happened to TextExpander but wow it isn’t good.

Looking around for a text expansion tool of some kind I came across espanso. It’s a snippet tool. It behaves about like yasnippet or TextMate snippets: Start typing a trigger phrase and it expands it for you.

Most of these things are wrapped in a GUI. espanso is configurable with a YAML file. It also has some cool stuff for handling regexps that allow you to use variables to your snippets. For instance:

- regex: ":tml\\((?P<tag>.*)\\)"
  replace: "[Items tagged `{{tag}}` in Things](things:///search?query=%23{{tag}})"

If you type :tml(foo) it’ll expand to a Markdown link using foo.

[Items tagged `foo` in Things](things:///search?query=%23foo)

(I’m not sure, btw, how to do that in yasnippet and need to figure that out.)