Frontmatter

Christofer Bäcklin, 2026-05-21

Go to app

Obsidian is used more and more by myself, colleagues, and teams around me. Currently I'm up to 5000 notes without including any LLM conversations, Jira tickets, or other context material. Having my brain on disk is all great and useful but I've increasingly started using it programatically, for querying, building automations, managing website content and apps on my platform. Well curated frontmatter is then key for reliability but is a pain to manage manually, especially if you need non-trivial refactoring.

It hit me that because you can query documents based on frontmatter with dataview, it should be straightforward to implement SQL-style data manipulation as well. SQL already covers the heavy lifting, can go complex without getting unwieldy, and everybody knows it. It brings structure and safety to big operations that plain search-and-replace don't without forcing a DSL on people.

Said and done, I'm happy to present the fm CLI tool:

-- Step 1: Inspect current state
select vegan, vegetarian, veggie from "recipes/*.md";

-- Step 2: Normalize fields (errors if casting fails)
update "recipes/*.md" set vegan:bool, vegetarian:bool, veggie:bool, diet:list;

-- Step 3: Populate `diet` from existing fields
update "recipes/*.md" set diet+="vegan"      where vegan;
update "recipes/*.md" set diet+="vegetarian" where vegetarian or veggie;

-- Step 4: Verify results
select vegan, vegetarian, veggie, diet from "recipes/*.md" limit 100;

-- Step 5: Clean up deprecated fields
alter "recipes/*.md" drop vegan, vegetarian, veggie;

Above snippet goes through my recipes and turns the flags vegan, vegetarian and veggie (synonymous with vegetarian) into a list property diet into which I can later add other types.

The fm SQL dialect follows BigQuery with the addition of shorthand typecasting (used in step 2 of the example).

AI assistance

Building fm with Claude Code was a somewhat different experience than hacking together a web app. The requirement on correctness is drastically higher since I hope that many people will use the software to manage their precious Obsidian Vaults. The code is also on public display on GitHub so I had to work hands-on with it quite a bit to ensure decent structure and remove slop.

Opus quickly got a working prototype in place, only supporting a few predefined query patterns. Widening the scope to a reasonably complete SQL dialect expressed in EBNF-like syntax took a lot of reasoning and up-front documentation before plunging into plans, testing strategy, and implementation. My previous experience and opinions on agentic coding best practice was confirmed yet again:

  1. Think first and discuss with agent. Sketch out the target architecture and user interface in as great detail as you can.
  2. Iterate implementation plan together with agent (don't blindly outsource!).
  3. Generate the core data structures and interfaces then review manually.
  4. Generate tests and review manually. Claude was way off in some areas forcing me to craft several tests by hand.
  5. Generate implementation.
  6. Acceptance test manually.

I don't try to eliminate the human-in-the-loop because I find that it would require too much effort in crafting instructions and tests and, more importantly, lose the creative process. If I want to make something generic or replicate someone else's software then it should be possible with enough token spend, but when making something new that I will put my name on and support going forward, then I need to explore the problem, learn and internalize the solution as the work unfolds.

Interestingly, despite defining the syntax tree data types up front, Sonnet failed miserably at implementing parsing on its own in one go. I solved it by ranking the different methods to-be-implemented by complexity and generating them iteratively from simplest to most complex. A human developer would have done such ranking intuitively from understanding the problem, but models of modest complexity do not.