skip to content

use cases / zettelkasten

tnotes for Zettelkasten

A terminal Zettelkasten with title-based [[links]], a backlinks overlay, nested #tags and CLI capture, plus an honest note on what it lacks: IDs, graph view.

The problem

A Zettelkasten lives or dies on friction. If writing a new note and linking it to two existing ones takes more than a few seconds, the notes stay in your head. If the tool hides the files behind a database, you cannot trust the box to outlive the app. Most terminal-friendly setups solve one of these: a fast editor with no link awareness, or a link-aware app that is not fast in a terminal.

tnotes handles the linking layer over plain .md files: [[Title]] links resolve by title, a completion popup appears after [[, and a backlinks overlay shows what points at the current note. The rest is deliberately absent, and this page says which parts.

Setup in three steps

  1. Choose a flat or shallow layout. Folders are directories on disk and the tree shows them, but a Zettelkasten usually wants structure to come from links and tags rather than hierarchy. Two folders, inbox and zettel, are enough:
roots = ["~/zettel"]

[editor]
template = "# "
autosave_ms = 300

[appearance]
compact = true
counts = true
  1. Keep the title on the first line. tnotes names the file after it (# Spaced repetition decays with interval becomes spaced-repetition-decays-with-interval.md) and rewrites [[old title]] to [[new title]] in every other note when it changes. This is what makes title-based links safe to refactor.

  2. Decide on tags. #tags are parsed from the body, and nested tags such as #topic/memory work, so a tag hierarchy can replace folders. ctrl+click on a tag filters the list to it; tnotes ls --tag topic/memory does the same from a shell.

Daily workflow

Capture. ctrl+t opens a new note with the template’s # in place; type the title, enter, write the idea. From a shell, the same is one line:

echo "Interval growth should be multiplicative, not additive. See [[Spaced repetition decays with interval]]." \
  | tnotes new "Ease factor" --tag topic/memory --folder inbox --stdin

new refuses a title that already exists unless you pass --duplicate. In a Zettelkasten that is a feature: two notes with the same title mean either a duplicate idea or a title that is not specific enough.

Link while writing. Type [[, a few letters, and tab to complete against every title in the vault. If the note you want does not exist yet, write the link anyway and press alt+enter on it: the target is created next to the current note and opened. Missing targets are how structure notes grow.

Follow backlinks. With the cursor on a line that has no link, alt+enter opens the backlinks overlay; the status bar shows ↩ N for the count. This is the “what else connects here” question, answered without leaving the editor.

Process the inbox. In the list, / filters, m moves the selected note from inbox to zettel, s changes the sort. Since the note’s id changes with its folder but the title does not, links keep working.

Audit from the shell. ls --json includes links and backlinks for every note, so orphan detection is a jq filter:

tnotes ls --json | jq -r '.[] | select(.backlinks == [] and .links == []) | .title'

Run it weekly. Orphans are either entry points that need a link from a structure note or ideas that were never connected.

What tnotes does not do

Pair it with

tnotes for…